The file C:\Users\MikeSwanson\Claude\guru-connect\agent\src\tray\mod.rs has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
    49→        let status_item = MenuItem::new(&status_text, false, None);
    50→        let machine_item = MenuItem::new(format!("Machine: {}", machine_name), false, None);
    51→        let separator = PredefinedMenuItem::separator();
    52→
    53→        // Only show "End Session" for support sessions
    54→        // Persistent agents can only be removed by admin
    55→        let end_session_item = if allow_end_session {
    56→            MenuItem::new("End Session", true, None)
    57→        } else {
    58→            MenuItem::new("Managed by Administrator", false, None)
    59→        };
    60→
    61→        // Debug window option (always available)
    62→        let debug_item = MenuItem::new("Show Debug Window", true, None);
    63→
    64→        // Build menu
    65→        let menu = Menu::new();
    66→        menu.append(&status_item)?;
    67→        menu.append(&machine_item)?;
    68→        menu.append(&separator)?;
    69→        menu.append(&debug_item)?;
    70→        menu.append(&end_session_item)?;
    71→
    72→        // Create tray icon
    73→        let icon = create_default_icon()?;
    74→

        // Only show "End Session" for support sessions
        // Persistent agents can only be removed by admin
        let end_session_item = if allow_end_session {
            MenuItem::new("End Session", true, None)
        } else {
            MenuItem::new("Managed by Administrator", false, None)
        };

        // Debug window option (always available)
        let debug_item = MenuItem::new("Show Debug Window", true, None);

        // Build menu
        let menu = Menu::new();
        menu.append(&status_item)?;
        menu.append(&machine_item)?;
        menu.append(&separator)?;
        menu.append(&debug_item)?;
        menu.append(&end_session_item)?;

//! System tray icon and menu for the agent
//!
//! Provides a tray icon with menu options:
//! - Connection status
//! - Machine name
//! - End session

use anyhow::Result;
use muda::{Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tray_icon::{Icon, TrayIcon, TrayIconBuilder, TrayIconEvent};
use tracing::{info, warn};

#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{
    PeekMessageW, TranslateMessage, DispatchMessageW, MSG, PM_REMOVE,
};

/// Events that can be triggered from the tray menu
#[derive(Debug, Clone)]
pub enum TrayAction {
    EndSession,
    ShowDetails,
    ShowDebugWindow,
}

/// Tray icon controller
pub struct TrayController {
    _tray_icon: TrayIcon,
    menu: Menu,
    end_session_item: MenuItem,
    debug_item: MenuItem,
    status_item: MenuItem,
    exit_requested: Arc<AtomicBool>,
}

impl TrayController {
    /// Create a new tray controller
    /// `allow_end_session` - If true, show "End Session" menu item (only for support sessions)
    pub fn new(machine_name: &str, support_code: Option<&str>, allow_end_session: bool) -> Result<Self> {
        // Create menu items
        let status_text = if let Some(code) = support_code {
            format!("Support Session: {}", code)
        } else {
            "Persistent Agent".to_string()
        };

        let status_item = MenuItem::new(&status_text, false, None);
        let machine_item = MenuItem::new(format!("Machine: {}", machine_name), false, None);
        let separator = PredefinedMenuItem::separator();

        // Only show "End Session" for support sessions
        // Persistent agents can only be removed by admin
        let end_session_item = if allow_end_session {
            MenuItem::new("End Session", true, None)
        } else {
            MenuItem::new("Managed by Administrator", false, None)
        };

        // Build menu
        let menu = Menu::new();
        menu.append(&status_item)?;
        menu.append(&machine_item)?;
        menu.append(&separator)?;
        menu.append(&end_session_item)?;

        // Create tray icon
        let icon = create_default_icon()?;

        let tray_icon = TrayIconBuilder::new()
            .with_menu(Box::new(menu.clone()))
            .with_tooltip(format!("GuruConnect - {}", machine_name))
            .with_icon(icon)
            .build()?;

        let exit_requested = Arc::new(AtomicBool::new(false));

        Ok(Self {
            _tray_icon: tray_icon,
            menu,
            end_session_item,
            status_item,
            exit_requested,
        })
    }

    /// Check if exit has been requested
    pub fn exit_requested(&self) -> bool {
        self.exit_requested.load(Ordering::SeqCst)
    }

    /// Update the connection status display
    pub fn update_status(&self, status: &str) {
        self.status_item.set_text(status);
    }

    /// Process pending menu events (call this from the main loop)
    pub fn process_events(&self) -> Option<TrayAction> {
        // Pump Windows message queue to process tray icon events
        #[cfg(windows)]
        pump_windows_messages();

        // Check for menu events
        if let Ok(event) = MenuEvent::receiver().try_recv() {
            if event.id == self.end_session_item.id() {
                info!("End session requested from tray menu");
                self.exit_requested.store(true, Ordering::SeqCst);
                return Some(TrayAction::EndSession);
            }
        }

        // Check for tray icon events (like double-click)
        if let Ok(event) = TrayIconEvent::receiver().try_recv() {
            match event {
                TrayIconEvent::DoubleClick { .. } => {
                    info!("Tray icon double-clicked");
                    return Some(TrayAction::ShowDetails);
                }
                _ => {}
            }
        }

        None
    }
}

/// Pump the Windows message queue to process tray icon events
#[cfg(windows)]
fn pump_windows_messages() {
    unsafe {
        let mut msg = MSG::default();
        // Process all pending messages
        while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
            let _ = TranslateMessage(&msg);
            DispatchMessageW(&msg);
        }
    }
}

/// Create a simple default icon (green circle for connected)
fn create_default_icon() -> Result<Icon> {
    // Create a simple 32x32 green icon
    let size = 32u32;
    let mut rgba = vec![0u8; (size * size * 4) as usize];

    let center = size as f32 / 2.0;
    let radius = size as f32 / 2.0 - 2.0;

    for y in 0..size {
        for x in 0..size {
            let dx = x as f32 - center;
            let dy = y as f32 - center;
            let dist = (dx * dx + dy * dy).sqrt();

            let idx = ((y * size + x) * 4) as usize;

            if dist <= radius {
                // Green circle
                rgba[idx] = 76;      // R
                rgba[idx + 1] = 175; // G
                rgba[idx + 2] = 80;  // B
                rgba[idx + 3] = 255; // A
            } else if dist <= radius + 1.0 {
                // Anti-aliased edge
                let alpha = ((radius + 1.0 - dist) * 255.0) as u8;
                rgba[idx] = 76;
                rgba[idx + 1] = 175;
                rgba[idx + 2] = 80;
                rgba[idx + 3] = alpha;
            }
        }
    }

    let icon = Icon::from_rgba(rgba, size, size)?;
    Ok(icon)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_create_icon() {
        let icon = create_default_icon();
        assert!(icon.is_ok());
    }
}

+        // Debug window option (always available)

+        let debug_item = MenuItem::new("Show Debug Window", true, None);


Now I need to update the return statement in new() to include the debug_item field.


Edit

        Ok(Self {
            _tray_icon: tray_icon,
            menu,
            end_session_item,
            status_item,
            exit_requested,
        })

        Ok(Self {
            _tray_icon: tray_icon,
            menu,
            end_session_item,
            debug_item,
            status_item,
            exit_requested,
        })


The file C:\Users\MikeSwanson\Claude\guru-connect\agent\src\tray\mod.rs has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
    79→            .build()?;
    80→
    81→        let exit_requested = Arc::new(AtomicBool::new(false));
    82→
    83→        Ok(Self {
    84→            _tray_icon: tray_icon,
    85→            menu,
    86→            end_session_item,
    87→            debug_item,
    88→            status_item,
    89→            exit_requested,
    90→        })
    91→    }
    92→
    93→    /// Check if exit has been requested
    94→    pub fn exit_requested(&self) -> bool {

        Ok(Self {
            _tray_icon: tray_icon,
            menu,
            end_session_item,
            debug_item,
            status_item,
            exit_requested,
        })

//! System tray icon and menu for the agent
//!
//! Provides a tray icon with menu options:
//! - Connection status
//! - Machine name
//! - End session

use anyhow::Result;
use muda::{Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tray_icon::{Icon, TrayIcon, TrayIconBuilder, TrayIconEvent};
use tracing::{info, warn};

#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{
    PeekMessageW, TranslateMessage, DispatchMessageW, MSG, PM_REMOVE,
};

/// Events that can be triggered from the tray menu
#[derive(Debug, Clone)]
pub enum TrayAction {
    EndSession,
    ShowDetails,
    ShowDebugWindow,
}

/// Tray icon controller
pub struct TrayController {
    _tray_icon: TrayIcon,
    menu: Menu,
    end_session_item: MenuItem,
    debug_item: MenuItem,
    status_item: MenuItem,
    exit_requested: Arc<AtomicBool>,
}

impl TrayController {
    /// Create a new tray controller
    /// `allow_end_session` - If true, show "End Session" menu item (only for support sessions)
    pub fn new(machine_name: &str, support_code: Option<&str>, allow_end_session: bool) -> Result<Self> {
        // Create menu items
        let status_text = if let Some(code) = support_code {
            format!("Support Session: {}", code)
        } else {
            "Persistent Agent".to_string()
        };

        let status_item = MenuItem::new(&status_text, false, None);
        let machine_item = MenuItem::new(format!("Machine: {}", machine_name), false, None);
        let separator = PredefinedMenuItem::separator();

        // Only show "End Session" for support sessions
        // Persistent agents can only be removed by admin
        let end_session_item = if allow_end_session {
            MenuItem::new("End Session", true, None)
        } else {
            MenuItem::new("Managed by Administrator", false, None)
        };

        // Debug window option (always available)
        let debug_item = MenuItem::new("Show Debug Window", true, None);

        // Build menu
        let menu = Menu::new();
        menu.append(&status_item)?;
        menu.append(&machine_item)?;
        menu.append(&separator)?;
        menu.append(&debug_item)?;
        menu.append(&end_session_item)?;

        // Create tray icon
        let icon = create_default_icon()?;

        let tray_icon = TrayIconBuilder::new()
            .with_menu(Box::new(menu.clone()))
            .with_tooltip(format!("GuruConnect - {}", machine_name))
            .with_icon(icon)
            .build()?;

        let exit_requested = Arc::new(AtomicBool::new(false));

        Ok(Self {
            _tray_icon: tray_icon,
            menu,
            end_session_item,
            status_item,
            exit_requested,
        })
    }

    /// Check if exit has been requested
    pub fn exit_requested(&self) -> bool {
        self.exit_requested.load(Ordering::SeqCst)
    }

    /// Update the connection status display
    pub fn update_status(&self, status: &str) {
        self.status_item.set_text(status);
    }

    /// Process pending menu events (call this from the main loop)
    pub fn process_events(&self) -> Option<TrayAction> {
        // Pump Windows message queue to process tray icon events
        #[cfg(windows)]
        pump_windows_messages();

        // Check for menu events
        if let Ok(event) = MenuEvent::receiver().try_recv() {
            if event.id == self.end_session_item.id() {
                info!("End session requested from tray menu");
                self.exit_requested.store(true, Ordering::SeqCst);
                return Some(TrayAction::EndSession);
            }
        }

        // Check for tray icon events (like double-click)
        if let Ok(event) = TrayIconEvent::receiver().try_recv() {
            match event {
                TrayIconEvent::DoubleClick { .. } => {
                    info!("Tray icon double-clicked");
                    return Some(TrayAction::ShowDetails);
                }
                _ => {}
            }
        }

        None
    }
}

/// Pump the Windows message queue to process tray icon events
#[cfg(windows)]
fn pump_windows_messages() {
    unsafe {
        let mut msg = MSG::default();
        // Process all pending messages
        while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
            let _ = TranslateMessage(&msg);
            DispatchMessageW(&msg);
        }
    }
}

/// Create a simple default icon (green circle for connected)
fn create_default_icon() -> Result<Icon> {
    // Create a simple 32x32 green icon
    let size = 32u32;
    let mut rgba = vec![0u8; (size * size * 4) as usize];

    let center = size as f32 / 2.0;
    let radius = size as f32 / 2.0 - 2.0;

    for y in 0..size {
        for x in 0..size {
            let dx = x as f32 - center;
            let dy = y as f32 - center;
            let dist = (dx * dx + dy * dy).sqrt();

            let idx = ((y * size + x) * 4) as usize;

            if dist <= radius {
                // Green circle
                rgba[idx] = 76;      // R
                rgba[idx + 1] = 175; // G
                rgba[idx + 2] = 80;  // B
                rgba[idx + 3] = 255; // A
            } else if dist <= radius + 1.0 {
                // Anti-aliased edge
                let alpha = ((radius + 1.0 - dist) * 255.0) as u8;
                rgba[idx] = 76;
                rgba[idx + 1] = 175;
                rgba[idx + 2] = 80;
                rgba[idx + 3] = alpha;
            }
        }
    }

    let icon = Icon::from_rgba(rgba, size, size)?;
    Ok(icon)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_create_icon() {
        let icon = create_default_icon();
        assert!(icon.is_ok());
    }
}


Now I need to update the process_events function to handle the debug_item click and return ShowDebugWindow action.


Now update the event handler to detect debug window clicks:


        // Check for menu events
        if let Ok(event) = MenuEvent::receiver().try_recv() {
            if event.id == self.end_session_item.id() {
                info!("End session requested from tray menu");
                self.exit_requested.store(true, Ordering::SeqCst);
                return Some(TrayAction::EndSession);
            }
        }

        // Check for menu events
        if let Ok(event) = MenuEvent::receiver().try_recv() {
            if event.id == self.end_session_item.id() {
                info!("End session requested from tray menu");
                self.exit_requested.store(true, Ordering::SeqCst);
                return Some(TrayAction::EndSession);
            }
            if event.id == self.debug_item.id() {
                info!("Debug window requested from tray menu");
                return Some(TrayAction::ShowDebugWindow);
            }
        }


The file C:\Users\MikeSwanson\Claude\guru-connect\agent\src\tray\mod.rs has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
   105→        // Pump Windows message queue to process tray icon events
   106→        #[cfg(windows)]
   107→        pump_windows_messages();
   108→
   109→        // Check for menu events
   110→        if let Ok(event) = MenuEvent::receiver().try_recv() {
   111→            if event.id == self.end_session_item.id() {
   112→                info!("End session requested from tray menu");
   113→                self.exit_requested.store(true, Ordering::SeqCst);
   114→                return Some(TrayAction::EndSession);
   115→            }
   116→            if event.id == self.debug_item.id() {
   117→                info!("Debug window requested from tray menu");
   118→                return Some(TrayAction::ShowDebugWindow);
   119→            }
   120→        }
   121→
   122→        // Check for tray icon events (like double-click)
   123→        if let Ok(event) = TrayIconEvent::receiver().try_recv() {
   124→            match event {

        // Check for menu events
        if let Ok(event) = MenuEvent::receiver().try_recv() {
            if event.id == self.end_session_item.id() {
                info!("End session requested from tray menu");
                self.exit_requested.store(true, Ordering::SeqCst);
                return Some(TrayAction::EndSession);
            }
            if event.id == self.debug_item.id() {
                info!("Debug window requested from tray menu");
                return Some(TrayAction::ShowDebugWindow);
            }
        }

//! System tray icon and menu for the agent
//!
//! Provides a tray icon with menu options:
//! - Connection status
//! - Machine name
//! - End session

use anyhow::Result;
use muda::{Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tray_icon::{Icon, TrayIcon, TrayIconBuilder, TrayIconEvent};
use tracing::{info, warn};

#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{
    PeekMessageW, TranslateMessage, DispatchMessageW, MSG, PM_REMOVE,
};

/// Events that can be triggered from the tray menu
#[derive(Debug, Clone)]
pub enum TrayAction {
    EndSession,
    ShowDetails,
    ShowDebugWindow,
}

/// Tray icon controller
pub struct TrayController {
    _tray_icon: TrayIcon,
    menu: Menu,
    end_session_item: MenuItem,
    debug_item: MenuItem,
    status_item: MenuItem,
    exit_requested: Arc<AtomicBool>,
}

impl TrayController {
    /// Create a new tray controller
    /// `allow_end_session` - If true, show "End Session" menu item (only for support sessions)
    pub fn new(machine_name: &str, support_code: Option<&str>, allow_end_session: bool) -> Result<Self> {
        // Create menu items
        let status_text = if let Some(code) = support_code {
            format!("Support Session: {}", code)
        } else {
            "Persistent Agent".to_string()
        };

        let status_item = MenuItem::new(&status_text, false, None);
        let machine_item = MenuItem::new(format!("Machine: {}", machine_name), false, None);
        let separator = PredefinedMenuItem::separator();

        // Only show "End Session" for support sessions
        // Persistent agents can only be removed by admin
        let end_session_item = if allow_end_session {
            MenuItem::new("End Session", true, None)
        } else {
            MenuItem::new("Managed by Administrator", false, None)
        };

        // Debug window option (always available)
        let debug_item = MenuItem::new("Show Debug Window", true, None);

        // Build menu
        let menu = Menu::new();
        menu.append(&status_item)?;
        menu.append(&machine_item)?;
        menu.append(&separator)?;
        menu.append(&debug_item)?;
        menu.append(&end_session_item)?;

        // Create tray icon
        let icon = create_default_icon()?;

        let tray_icon = TrayIconBuilder::new()
            .with_menu(Box::new(menu.clone()))
            .with_tooltip(format!("GuruConnect - {}", machine_name))
            .with_icon(icon)
            .build()?;

        let exit_requested = Arc::new(AtomicBool::new(false));

        Ok(Self {
            _tray_icon: tray_icon,
            menu,
            end_session_item,
            debug_item,
            status_item,
            exit_requested,
        })
    }

    /// Check if exit has been requested
    pub fn exit_requested(&self) -> bool {
        self.exit_requested.load(Ordering::SeqCst)
    }

    /// Update the connection status display
    pub fn update_status(&self, status: &str) {
        self.status_item.set_text(status);
    }

    /// Process pending menu events (call this from the main loop)
    pub fn process_events(&self) -> Option<TrayAction> {
        // Pump Windows message queue to process tray icon events
        #[cfg(windows)]
        pump_windows_messages();

        // Check for menu events
        if let Ok(event) = MenuEvent::receiver().try_recv() {
            if event.id == self.end_session_item.id() {
                info!("End session requested from tray menu");
                self.exit_requested.store(true, Ordering::SeqCst);
                return Some(TrayAction::EndSession);
            }
        }

        // Check for tray icon events (like double-click)
        if let Ok(event) = TrayIconEvent::receiver().try_recv() {
            match event {
                TrayIconEvent::DoubleClick { .. } => {
                    info!("Tray icon double-clicked");
                    return Some(TrayAction::ShowDetails);
                }
                _ => {}
            }
        }

        None
    }
}

/// Pump the Windows message queue to process tray icon events
#[cfg(windows)]
fn pump_windows_messages() {
    unsafe {
        let mut msg = MSG::default();
        // Process all pending messages
        while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
            let _ = TranslateMessage(&msg);
            DispatchMessageW(&msg);
        }
    }
}

/// Create a simple default icon (green circle for connected)
fn create_default_icon() -> Result<Icon> {
    // Create a simple 32x32 green icon
    let size = 32u32;
    let mut rgba = vec![0u8; (size * size * 4) as usize];

    let center = size as f32 / 2.0;
    let radius = size as f32 / 2.0 - 2.0;

    for y in 0..size {
        for x in 0..size {
            let dx = x as f32 - center;
            let dy = y as f32 - center;
            let dist = (dx * dx + dy * dy).sqrt();

            let idx = ((y * size + x) * 4) as usize;

            if dist <= radius {
                // Green circle
                rgba[idx] = 76;      // R
                rgba[idx + 1] = 175; // G
                rgba[idx + 2] = 80;  // B
                rgba[idx + 3] = 255; // A
            } else if dist <= radius + 1.0 {
                // Anti-aliased edge
                let alpha = ((radius + 1.0 - dist) * 255.0) as u8;
                rgba[idx] = 76;
                rgba[idx + 1] = 175;
                rgba[idx + 2] = 80;
                rgba[idx + 3] = alpha;
            }
        }
    }

    let icon = Icon::from_rgba(rgba, size, size)?;
    Ok(icon)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_create_icon() {
        let icon = create_default_icon();
        assert!(icon.is_ok());
    }
}

+            if event.id == self.debug_item.id() {

+                info!("Debug window requested from tray menu");

+                return Some(TrayAction::ShowDebugWindow);


Now I need to add a function to show the debug console window, and handle the ShowDebugWindow action in main.rs or session/mod.rs.Let me add a function to allocate a console window and then handle it in the session loop.


Now let me add a function to show the debug console and handle it in main.rs:


#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_OK, MB_ICONINFORMATION};
#[cfg(windows)]
use windows::core::PCWSTR;
#[cfg(windows)]
use windows::Win32::Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY};
#[cfg(windows)]
use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};

#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_OK, MB_ICONINFORMATION};
#[cfg(windows)]
use windows::core::PCWSTR;
#[cfg(windows)]
use windows::Win32::Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY};
#[cfg(windows)]
use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
#[cfg(windows)]
use windows::Win32::System::Console::{AllocConsole, FreeConsole, GetConsoleWindow};
#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_SHOW, SW_HIDE};


The file C:\Users\MikeSwanson\Claude\guru-connect\agent\src\main.rs has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
    28→use anyhow::Result;
    29→use tracing::{info, error, warn, Level};
    30→use tracing_subscriber::FmtSubscriber;
    31→
    32→#[cfg(windows)]
    33→use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_OK, MB_ICONINFORMATION};
    34→#[cfg(windows)]
    35→use windows::core::PCWSTR;
    36→#[cfg(windows)]
    37→use windows::Win32::Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY};
    38→#[cfg(windows)]
    39→use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
    40→#[cfg(windows)]
    41→use windows::Win32::System::Console::{AllocConsole, FreeConsole, GetConsoleWindow};
    42→#[cfg(windows)]
    43→use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_SHOW, SW_HIDE};
    44→
    45→/// Extract a 6-digit support code from the executable's filename.
    46→/// Looks for patterns like "GuruConnect-123456.exe" or "123456.exe"
    47→fn extract_code_from_filename() -> Option<String> {

#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_OK, MB_ICONINFORMATION};
#[cfg(windows)]
use windows::core::PCWSTR;
#[cfg(windows)]
use windows::Win32::Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY};
#[cfg(windows)]
use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
#[cfg(windows)]
use windows::Win32::System::Console::{AllocConsole, FreeConsole, GetConsoleWindow};
#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_SHOW, SW_HIDE};

//! GuruConnect Agent - Remote Desktop Agent for Windows
//!
//! Provides screen capture, input injection, and remote control capabilities.
//!
//! Usage:
//!   guruconnect-agent.exe [support_code]
//!
//! If a support code is provided, the agent will connect using that code
//! for a one-time support session.

// Hide console window by default on Windows (release builds)
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

mod capture;
mod chat;
mod config;
mod encoder;
mod input;
mod session;
mod startup;
mod transport;
mod tray;

pub mod proto {
    include!(concat!(env!("OUT_DIR"), "/guruconnect.rs"));
}

use anyhow::Result;
use tracing::{info, error, warn, Level};
use tracing_subscriber::FmtSubscriber;

#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_OK, MB_ICONINFORMATION};
#[cfg(windows)]
use windows::core::PCWSTR;
#[cfg(windows)]
use windows::Win32::Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY};
#[cfg(windows)]
use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};

/// Extract a 6-digit support code from the executable's filename.
/// Looks for patterns like "GuruConnect-123456.exe" or "123456.exe"
fn extract_code_from_filename() -> Option<String> {
    // Get the path to the current executable
    let exe_path = std::env::current_exe().ok()?;
    let filename = exe_path.file_stem()?.to_str()?;

    // Look for a 6-digit number in the filename
    // Try common patterns: "Name-123456", "Name_123456", "123456"
    for part in filename.split(|c| c == '-' || c == '_' || c == '.') {
        let trimmed = part.trim();
        if trimmed.len() == 6 && trimmed.chars().all(|c| c.is_ascii_digit()) {
            return Some(trimmed.to_string());
        }
    }

    // Also check if the last 6 characters are digits (e.g., "GuruConnect123456")
    if filename.len() >= 6 {
        let last_six = &filename[filename.len() - 6..];
        if last_six.chars().all(|c| c.is_ascii_digit()) {
            return Some(last_six.to_string());
        }
    }

    None
}

/// Check if the process is running with elevated privileges (Windows only)
#[cfg(windows)]
fn is_elevated() -> bool {
    unsafe {
        let mut token_handle = windows::Win32::Foundation::HANDLE::default();
        if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token_handle).is_err() {
            return false;
        }

        let mut elevation = TOKEN_ELEVATION::default();
        let mut size = std::mem::size_of::<TOKEN_ELEVATION>() as u32;

        let result = GetTokenInformation(
            token_handle,
            TokenElevation,
            Some(&mut elevation as *mut _ as *mut _),
            size,
            &mut size,
        );

        let _ = windows::Win32::Foundation::CloseHandle(token_handle);

        result.is_ok() && elevation.TokenIsElevated != 0
    }
}

#[cfg(not(windows))]
fn is_elevated() -> bool {
    // On non-Windows, check if running as root
    unsafe { libc::geteuid() == 0 }
}

/// Show a message box to the user (Windows only)
#[cfg(windows)]
fn show_message_box(title: &str, message: &str) {
    use std::ffi::OsStr;
    use std::os::windows::ffi::OsStrExt;

    // Convert strings to wide strings for Windows API
    let title_wide: Vec<u16> = OsStr::new(title)
        .encode_wide()
        .chain(std::iter::once(0))
        .collect();
    let message_wide: Vec<u16> = OsStr::new(message)
        .encode_wide()
        .chain(std::iter::once(0))
        .collect();

    unsafe {
        MessageBoxW(
            None,
            PCWSTR(message_wide.as_ptr()),
            PCWSTR(title_wide.as_ptr()),
            MB_OK | MB_ICONINFORMATION,
        );
    }
}

#[cfg(not(windows))]
fn show_message_box(_title: &str, _message: &str) {
    // No-op on non-Windows platforms
}

#[tokio::main]
async fn main() -> Result<()> {
    // Initialize logging
    let subscriber = FmtSubscriber::builder()
        .with_max_level(Level::INFO)
        .with_target(true)
        .with_thread_ids(true)
        .init();

    info!("GuruConnect Agent v{}", env!("CARGO_PKG_VERSION"));

    // Check and log elevation status
    if is_elevated() {
        info!("Running with elevated (administrator) privileges");
    } else {
        info!("Running with standard user privileges");
    }

    // Extract support code from executable filename
    // e.g., GuruConnect-123456.exe -> 123456
    let support_code = extract_code_from_filename();
    if let Some(ref code) = support_code {
        info!("Support code from filename: {}", code);
    }

    // Load configuration
    let mut config = config::Config::load()?;
    config.support_code = support_code;
    info!("Loaded configuration for server: {}", config.server_url);

    // Run the agent
    if let Err(e) = run_agent(config).await {
        error!("Agent error: {}", e);
        return Err(e);
    }

    Ok(())
}

/// Clean up before exiting (remove from startup, etc.)
/// Called when user explicitly ends session or support session completes
fn cleanup_on_exit() {
    info!("Cleaning up before exit");
    if let Err(e) = startup::remove_from_startup() {
        warn!("Failed to remove from startup: {}", e);
    }
}

async fn run_agent(config: config::Config) -> Result<()> {
    // Create session manager
    let mut session = session::SessionManager::new(config.clone());
    let is_support_session = config.support_code.is_some();
    let hostname = config.hostname();

    // Add to startup so we reconnect after reboot
    // Persistent agents (no support code) should ALWAYS be in startup
    // Support sessions only need startup temporarily while active
    if let Err(e) = startup::add_to_startup() {
        warn!("Failed to add to startup: {}. Agent won't persist through reboot.", e);
    }

    // Create tray icon
    // Only support sessions can be ended by user - persistent agents are admin-managed
    let tray = match tray::TrayController::new(&hostname, config.support_code.as_deref(), is_support_session) {
        Ok(t) => {
            info!("Tray icon created");
            Some(t)
        }
        Err(e) => {
            warn!("Failed to create tray icon: {}. Continuing without tray.", e);
            None
        }
    };

    // Create chat controller
    let chat_ctrl = chat::ChatController::new();
    if chat_ctrl.is_some() {
        info!("Chat controller created");
    }

    // Connect to server and run main loop
    loop {
        info!("Connecting to server...");

        // Check if user requested exit via tray before connecting (support sessions only)
        if is_support_session {
            if let Some(ref t) = tray {
                if t.exit_requested() {
                    info!("Exit requested by user");
                    cleanup_on_exit();
                    return Ok(());
                }
            }
        }

        match session.connect().await {
            Ok(_) => {
                info!("Connected to server");

                // Update tray status
                if let Some(ref t) = tray {
                    t.update_status("Status: Connected");
                }

                // Run session until disconnect, passing tray and chat for event processing
                if let Err(e) = session.run_with_tray(tray.as_ref(), chat_ctrl.as_ref()).await {
                    let error_msg = e.to_string();

                    // Check if this is a user-initiated exit
                    if error_msg.contains("USER_EXIT") {
                        info!("Session ended by user");
                        cleanup_on_exit();
                        return Ok(());
                    }

                    // Check if this is a cancellation
                    if error_msg.contains("SESSION_CANCELLED") {
                        info!("Session was cancelled by technician");
                        cleanup_on_exit();
                        show_message_box(
                            "Support Session Ended",
                            "The support session was cancelled by the technician.\n\nThis window will close automatically.",
                        );
                        // Exit cleanly without reconnecting
                        return Ok(());
                    }

                    // Check if this is an admin disconnect (uninstall)
                    if error_msg.contains("ADMIN_DISCONNECT") {
                        info!("Session was disconnected by administrator - uninstalling");
                        if let Err(e) = startup::uninstall() {
                            warn!("Uninstall failed: {}", e);
                        }
                        show_message_box(
                            "Remote Session Ended",
                            "The remote support session has been ended by the administrator.\n\nThe agent will be removed from this computer.",
                        );
                        return Ok(());
                    }

                    error!("Session error: {}", e);
                }
            }
            Err(e) => {
                let error_msg = e.to_string();

                // Check if connection was rejected due to cancelled code
                if error_msg.contains("cancelled") {
                    info!("Support code was cancelled before connection");
                    cleanup_on_exit();
                    show_message_box(
                        "Support Session Cancelled",
                        "This support session has been cancelled.\n\nPlease contact your technician for a new support code.",
                    );
                    // Exit cleanly without reconnecting
                    return Ok(());
                }

                error!("Connection failed: {}", e);
            }
        }

        // For support sessions, don't reconnect if something goes wrong
        if is_support_session {
            info!("Support session ended, not reconnecting");
            cleanup_on_exit();
            return Ok(());
        }

        // Wait before reconnecting (persistent agents only - support sessions already exited above)
        info!("Reconnecting in 5 seconds...");
        tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
    }
}

+use windows::Win32::System::Console::{AllocConsole, FreeConsole, GetConsoleWindow};

+use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_SHOW, SW_HIDE};


Now let me add a function to show/toggle the debug console window.


Now add a function to show the debug console:


#[cfg(not(windows))]
fn show_message_box(_title: &str, _message: &str) {
    // No-op on non-Windows platforms
}

#[cfg(not(windows))]
fn show_message_box(_title: &str, _message: &str) {
    // No-op on non-Windows platforms
}

/// Show the debug console window (Windows only)
#[cfg(windows)]
fn show_debug_console() {
    unsafe {
        // Check if we already have a console
        let hwnd = GetConsoleWindow();
        if hwnd.0 == std::ptr::null_mut() {
            // No console, allocate one
            let _ = AllocConsole();
            info!("Debug console window opened");
        } else {
            // Console exists, make sure it's visible
            let _ = ShowWindow(hwnd, SW_SHOW);
            info!("Debug console window shown");
        }
    }
}

#[cfg(not(windows))]
fn show_debug_console() {
    // No-op on non-Windows platforms
}


The file C:\Users\MikeSwanson\Claude\guru-connect\agent\src\main.rs has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
   126→        );
   127→    }
   128→}
   129→
   130→#[cfg(not(windows))]
   131→fn show_message_box(_title: &str, _message: &str) {
   132→    // No-op on non-Windows platforms
   133→}
   134→
   135→/// Show the debug console window (Windows only)
   136→#[cfg(windows)]
   137→fn show_debug_console() {
   138→    unsafe {
   139→        // Check if we already have a console
   140→        let hwnd = GetConsoleWindow();
   141→        if hwnd.0 == std::ptr::null_mut() {
   142→            // No console, allocate one
   143→            let _ = AllocConsole();
   144→            info!("Debug console window opened");
   145→        } else {
   146→            // Console exists, make sure it's visible
   147→            let _ = ShowWindow(hwnd, SW_SHOW);
   148→            info!("Debug console window shown");
   149→        }
   150→    }
   151→}
   152→
   153→#[cfg(not(windows))]
   154→fn show_debug_console() {
   155→    // No-op on non-Windows platforms
   156→}
   157→
   158→#[tokio::main]
   159→async fn main() -> Result<()> {
   160→    // Initialize logging

#[cfg(not(windows))]
fn show_message_box(_title: &str, _message: &str) {
    // No-op on non-Windows platforms
}

/// Show the debug console window (Windows only)
#[cfg(windows)]
fn show_debug_console() {
    unsafe {
        // Check if we already have a console
        let hwnd = GetConsoleWindow();
        if hwnd.0 == std::ptr::null_mut() {
            // No console, allocate one
            let _ = AllocConsole();
            info!("Debug console window opened");
        } else {
            // Console exists, make sure it's visible
            let _ = ShowWindow(hwnd, SW_SHOW);
            info!("Debug console window shown");
        }
    }
}

#[cfg(not(windows))]
fn show_debug_console() {
    // No-op on non-Windows platforms
}

//! GuruConnect Agent - Remote Desktop Agent for Windows
//!
//! Provides screen capture, input injection, and remote control capabilities.
//!
//! Usage:
//!   guruconnect-agent.exe [support_code]
//!
//! If a support code is provided, the agent will connect using that code
//! for a one-time support session.

// Hide console window by default on Windows (release builds)
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

mod capture;
mod chat;
mod config;
mod encoder;
mod input;
mod session;
mod startup;
mod transport;
mod tray;

pub mod proto {
    include!(concat!(env!("OUT_DIR"), "/guruconnect.rs"));
}

use anyhow::Result;
use tracing::{info, error, warn, Level};
use tracing_subscriber::FmtSubscriber;

#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_OK, MB_ICONINFORMATION};
#[cfg(windows)]
use windows::core::PCWSTR;
#[cfg(windows)]
use windows::Win32::Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY};
#[cfg(windows)]
use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
#[cfg(windows)]
use windows::Win32::System::Console::{AllocConsole, FreeConsole, GetConsoleWindow};
#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_SHOW, SW_HIDE};

/// Extract a 6-digit support code from the executable's filename.
/// Looks for patterns like "GuruConnect-123456.exe" or "123456.exe"
fn extract_code_from_filename() -> Option<String> {
    // Get the path to the current executable
    let exe_path = std::env::current_exe().ok()?;
    let filename = exe_path.file_stem()?.to_str()?;

    // Look for a 6-digit number in the filename
    // Try common patterns: "Name-123456", "Name_123456", "123456"
    for part in filename.split(|c| c == '-' || c == '_' || c == '.') {
        let trimmed = part.trim();
        if trimmed.len() == 6 && trimmed.chars().all(|c| c.is_ascii_digit()) {
            return Some(trimmed.to_string());
        }
    }

    // Also check if the last 6 characters are digits (e.g., "GuruConnect123456")
    if filename.len() >= 6 {
        let last_six = &filename[filename.len() - 6..];
        if last_six.chars().all(|c| c.is_ascii_digit()) {
            return Some(last_six.to_string());
        }
    }

    None
}

/// Check if the process is running with elevated privileges (Windows only)
#[cfg(windows)]
fn is_elevated() -> bool {
    unsafe {
        let mut token_handle = windows::Win32::Foundation::HANDLE::default();
        if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token_handle).is_err() {
            return false;
        }

        let mut elevation = TOKEN_ELEVATION::default();
        let mut size = std::mem::size_of::<TOKEN_ELEVATION>() as u32;

        let result = GetTokenInformation(
            token_handle,
            TokenElevation,
            Some(&mut elevation as *mut _ as *mut _),
            size,
            &mut size,
        );

        let _ = windows::Win32::Foundation::CloseHandle(token_handle);

        result.is_ok() && elevation.TokenIsElevated != 0
    }
}

#[cfg(not(windows))]
fn is_elevated() -> bool {
    // On non-Windows, check if running as root
    unsafe { libc::geteuid() == 0 }
}

/// Show a message box to the user (Windows only)
#[cfg(windows)]
fn show_message_box(title: &str, message: &str) {
    use std::ffi::OsStr;
    use std::os::windows::ffi::OsStrExt;

    // Convert strings to wide strings for Windows API
    let title_wide: Vec<u16> = OsStr::new(title)
        .encode_wide()
        .chain(std::iter::once(0))
        .collect();
    let message_wide: Vec<u16> = OsStr::new(message)
        .encode_wide()
        .chain(std::iter::once(0))
        .collect();

    unsafe {
        MessageBoxW(
            None,
            PCWSTR(message_wide.as_ptr()),
            PCWSTR(title_wide.as_ptr()),
            MB_OK | MB_ICONINFORMATION,
        );
    }
}

#[cfg(not(windows))]
fn show_message_box(_title: &str, _message: &str) {
    // No-op on non-Windows platforms
}

#[tokio::main]
async fn main() -> Result<()> {
    // Initialize logging
    let subscriber = FmtSubscriber::builder()
        .with_max_level(Level::INFO)
        .with_target(true)
        .with_thread_ids(true)
        .init();

    info!("GuruConnect Agent v{}", env!("CARGO_PKG_VERSION"));

    // Check and log elevation status
    if is_elevated() {
        info!("Running with elevated (administrator) privileges");
    } else {
        info!("Running with standard user privileges");
    }

    // Extract support code from executable filename
    // e.g., GuruConnect-123456.exe -> 123456
    let support_code = extract_code_from_filename();
    if let Some(ref code) = support_code {
        info!("Support code from filename: {}", code);
    }

    // Load configuration
    let mut config = config::Config::load()?;
    config.support_code = support_code;
    info!("Loaded configuration for server: {}", config.server_url);

    // Run the agent
    if let Err(e) = run_agent(config).await {
        error!("Agent error: {}", e);
        return Err(e);
    }

    Ok(())
}

/// Clean up before exiting (remove from startup, etc.)
/// Called when user explicitly ends session or support session completes
fn cleanup_on_exit() {
    info!("Cleaning up before exit");
    if let Err(e) = startup::remove_from_startup() {
        warn!("Failed to remove from startup: {}", e);
    }
}

async fn run_agent(config: config::Config) -> Result<()> {
    // Create session manager
    let mut session = session::SessionManager::new(config.clone());
    let is_support_session = config.support_code.is_some();
    let hostname = config.hostname();

    // Add to startup so we reconnect after reboot
    // Persistent agents (no support code) should ALWAYS be in startup
    // Support sessions only need startup temporarily while active
    if let Err(e) = startup::add_to_startup() {
        warn!("Failed to add to startup: {}. Agent won't persist through reboot.", e);
    }

    // Create tray icon
    // Only support sessions can be ended by user - persistent agents are admin-managed
    let tray = match tray::TrayController::new(&hostname, config.support_code.as_deref(), is_support_session) {
        Ok(t) => {
            info!("Tray icon created");
            Some(t)
        }
        Err(e) => {
            warn!("Failed to create tray icon: {}. Continuing without tray.", e);
            None
        }
    };

    // Create chat controller
    let chat_ctrl = chat::ChatController::new();
    if chat_ctrl.is_some() {
        info!("Chat controller created");
    }

    // Connect to server and run main loop
    loop {
        info!("Connecting to server...");

        // Check if user requested exit via tray before connecting (support sessions only)
        if is_support_session {
            if let Some(ref t) = tray {
                if t.exit_requested() {
                    info!("Exit requested by user");
                    cleanup_on_exit();
                    return Ok(());
                }
            }
        }

        match session.connect().await {
            Ok(_) => {
                info!("Connected to server");

                // Update tray status
                if let Some(ref t) = tray {
                    t.update_status("Status: Connected");
                }

                // Run session until disconnect, passing tray and chat for event processing
                if let Err(e) = session.run_with_tray(tray.as_ref(), chat_ctrl.as_ref()).await {
                    let error_msg = e.to_string();

                    // Check if this is a user-initiated exit
                    if error_msg.contains("USER_EXIT") {
                        info!("Session ended by user");
                        cleanup_on_exit();
                        return Ok(());
                    }

                    // Check if this is a cancellation
                    if error_msg.contains("SESSION_CANCELLED") {
                        info!("Session was cancelled by technician");
                        cleanup_on_exit();
                        show_message_box(
                            "Support Session Ended",
                            "The support session was cancelled by the technician.\n\nThis window will close automatically.",
                        );
                        // Exit cleanly without reconnecting
                        return Ok(());
                    }

                    // Check if this is an admin disconnect (uninstall)
                    if error_msg.contains("ADMIN_DISCONNECT") {
                        info!("Session was disconnected by administrator - uninstalling");
                        if let Err(e) = startup::uninstall() {
                            warn!("Uninstall failed: {}", e);
                        }
                        show_message_box(
                            "Remote Session Ended",
                            "The remote support session has been ended by the administrator.\n\nThe agent will be removed from this computer.",
                        );
                        return Ok(());
                    }

                    error!("Session error: {}", e);
                }
            }
            Err(e) => {
                let error_msg = e.to_string();

                // Check if connection was rejected due to cancelled code
                if error_msg.contains("cancelled") {
                    info!("Support code was cancelled before connection");
                    cleanup_on_exit();
                    show_message_box(
                        "Support Session Cancelled",
                        "This support session has been cancelled.\n\nPlease contact your technician for a new support code.",
                    );
                    // Exit cleanly without reconnecting
                    return Ok(());
                }

                error!("Connection failed: {}", e);
            }
        }

        // For support sessions, don't reconnect if something goes wrong
        if is_support_session {
            info!("Support session ended, not reconnecting");
            cleanup_on_exit();
            return Ok(());
        }

        // Wait before reconnecting (persistent agents only - support sessions already exited above)
        info!("Reconnecting in 5 seconds...");
        tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
    }
}

+/// Show the debug console window (Windows only)

+        // Check if we already have a console

+        if hwnd.0 == std::ptr::null_mut() {

+            // No console, allocate one

+            info!("Debug console window opened");

+            // Console exists, make sure it's visible

+            let _ = ShowWindow(hwnd, SW_SHOW);

+            info!("Debug console window shown");


Now I need to handle the ShowDebugWindow action in the session loop. Let me find where TrayAction is handled in session/mod.rs.


Now handle the ShowDebugWindow action in the session module:


                        TrayAction::ShowDetails => {
                            // TODO: Show a details dialog
                            tracing::info!("User requested details (not yet implemented)");
                        }

                        TrayAction::ShowDetails => {
                            // TODO: Show a details dialog
                            tracing::info!("User requested details (not yet implemented)");
                        }
                        TrayAction::ShowDebugWindow => {
                            // Handled by caller
                        }


The file C:\Users\MikeSwanson\Claude\guru-connect\agent\src\session\mod.rs has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
   190→                        TrayAction::EndSession => {
   191→                            tracing::info!("User requested session end via tray");
   192→                            return Err(anyhow::anyhow!("USER_EXIT: Session ended by user"));
   193→                        }
   194→                        TrayAction::ShowDetails => {
   195→                            // TODO: Show a details dialog
   196→                            tracing::info!("User requested details (not yet implemented)");
   197→                        }
   198→                        TrayAction::ShowDebugWindow => {
   199→                            // Handled by caller
   200→                        }
   201→                    }
   202→                }
   203→
   204→                // Check if exit was requested

                        TrayAction::ShowDetails => {
                            // TODO: Show a details dialog
                            tracing::info!("User requested details (not yet implemented)");
                        }
                        TrayAction::ShowDebugWindow => {
                            // Handled by caller
                        }

//! Session management for the agent
//!
//! Handles the lifecycle of a remote session including:
//! - Connection to server
//! - Authentication
//! - Frame capture and encoding loop
//! - Input event handling

use crate::capture::{self, Capturer, Display};
use crate::chat::{ChatController, ChatMessage as ChatMsg};
use crate::config::Config;
use crate::encoder::{self, Encoder};
use crate::input::InputController;
use crate::proto::{Message, message, ChatMessage};
use crate::transport::WebSocketTransport;
use crate::tray::{TrayController, TrayAction};
use anyhow::Result;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::mpsc;

/// Session manager handles the remote control session
pub struct SessionManager {
    config: Config,
    transport: Option<WebSocketTransport>,
    state: SessionState,
}

#[derive(Debug, Clone, PartialEq)]
enum SessionState {
    Disconnected,
    Connecting,
    Connected,
    Active,
}

impl SessionManager {
    /// Create a new session manager
    pub fn new(config: Config) -> Self {
        Self {
            config,
            transport: None,
            state: SessionState::Disconnected,
        }
    }

    /// Connect to the server
    pub async fn connect(&mut self) -> Result<()> {
        self.state = SessionState::Connecting;

        let hostname = self.config.hostname();
        let transport = WebSocketTransport::connect(
            &self.config.server_url,
            &self.config.agent_id,
            &self.config.api_key,
            Some(&hostname),
            self.config.support_code.as_deref(),
        ).await?;

        self.transport = Some(transport);
        self.state = SessionState::Connected;

        Ok(())
    }

    /// Run the session main loop
    pub async fn run(&mut self) -> Result<()> {
        if self.transport.is_none() {
            anyhow::bail!("Not connected");
        }

        self.state = SessionState::Active;

        // Get primary display
        let primary_display = capture::primary_display()?;
        tracing::info!("Using display: {} ({}x{})", primary_display.name, primary_display.width, primary_display.height);

        // Create capturer
        let mut capturer = capture::create_capturer(
            primary_display.clone(),
            self.config.capture.use_dxgi,
            self.config.capture.gdi_fallback,
        )?;

        // Create encoder
        let mut encoder = encoder::create_encoder(
            &self.config.encoding.codec,
            self.config.encoding.quality,
        )?;

        // Create input controller
        let mut input = InputController::new()?;

        // Calculate frame interval
        let frame_interval = Duration::from_millis(1000 / self.config.capture.fps as u64);
        let mut last_frame_time = Instant::now();

        // Main loop
        loop {
            // Check for incoming messages (non-blocking)
            // Collect messages first, then release borrow before handling
            let messages: Vec<Message> = {
                let transport = self.transport.as_mut().unwrap();
                let mut msgs = Vec::new();
                while let Some(msg) = transport.try_recv()? {
                    msgs.push(msg);
                }
                msgs
            };

            for msg in messages {
                self.handle_message(&mut input, msg)?;
            }

            // Capture and send frame if interval elapsed
            if last_frame_time.elapsed() >= frame_interval {
                last_frame_time = Instant::now();

                if let Some(frame) = capturer.capture()? {
                    let encoded = encoder.encode(&frame)?;

                    // Skip empty frames (no changes)
                    if encoded.size > 0 {
                        let msg = Message {
                            payload: Some(message::Payload::VideoFrame(encoded.frame)),
                        };
                        let transport = self.transport.as_mut().unwrap();
                        transport.send(msg).await?;
                    }
                }
            }

            // Small sleep to prevent busy loop
            tokio::time::sleep(Duration::from_millis(1)).await;

            // Check if still connected
            if let Some(transport) = self.transport.as_ref() {
                if !transport.is_connected() {
                    tracing::warn!("Connection lost");
                    break;
                }
            } else {
                tracing::warn!("Transport is None");
                break;
            }
        }

        self.state = SessionState::Disconnected;
        Ok(())
    }

    /// Run the session main loop with tray and chat event processing
    pub async fn run_with_tray(&mut self, tray: Option<&TrayController>, chat: Option<&ChatController>) -> Result<()> {
        if self.transport.is_none() {
            anyhow::bail!("Not connected");
        }

        self.state = SessionState::Active;

        // Get primary display
        let primary_display = capture::primary_display()?;
        tracing::info!("Using display: {} ({}x{})", primary_display.name, primary_display.width, primary_display.height);

        // Create capturer
        let mut capturer = capture::create_capturer(
            primary_display.clone(),
            self.config.capture.use_dxgi,
            self.config.capture.gdi_fallback,
        )?;

        // Create encoder
        let mut encoder = encoder::create_encoder(
            &self.config.encoding.codec,
            self.config.encoding.quality,
        )?;

        // Create input controller
        let mut input = InputController::new()?;

        // Calculate frame interval
        let frame_interval = Duration::from_millis(1000 / self.config.capture.fps as u64);
        let mut last_frame_time = Instant::now();

        // Main loop
        loop {
            // Process tray events
            if let Some(t) = tray {
                if let Some(action) = t.process_events() {
                    match action {
                        TrayAction::EndSession => {
                            tracing::info!("User requested session end via tray");
                            return Err(anyhow::anyhow!("USER_EXIT: Session ended by user"));
                        }
                        TrayAction::ShowDetails => {
                            // TODO: Show a details dialog
                            tracing::info!("User requested details (not yet implemented)");
                        }
                    }
                }

                // Check if exit was requested
                if t.exit_requested() {
                    tracing::info!("Exit requested via tray");
                    return Err(anyhow::anyhow!("USER_EXIT: Exit requested by user"));
                }
            }

            // Check for incoming messages (non-blocking)
            let messages: Vec<Message> = {
                let transport = self.transport.as_mut().unwrap();
                let mut msgs = Vec::new();
                while let Some(msg) = transport.try_recv()? {
                    msgs.push(msg);
                }
                msgs
            };

            for msg in messages {
                // Handle chat messages specially
                if let Some(message::Payload::ChatMessage(chat_msg)) = &msg.payload {
                    if let Some(c) = chat {
                        c.add_message(ChatMsg {
                            id: chat_msg.id.clone(),
                            sender: chat_msg.sender.clone(),
                            content: chat_msg.content.clone(),
                            timestamp: chat_msg.timestamp,
                        });
                    }
                    continue; // Don't pass to handle_message
                }
                self.handle_message(&mut input, msg)?;
            }

            // Check for outgoing chat messages from user
            if let Some(c) = chat {
                if let Some(outgoing) = c.poll_outgoing() {
                    let chat_proto = ChatMessage {
                        id: outgoing.id,
                        sender: "client".to_string(),
                        content: outgoing.content,
                        timestamp: outgoing.timestamp,
                    };
                    let msg = Message {
                        payload: Some(message::Payload::ChatMessage(chat_proto)),
                    };
                    let transport = self.transport.as_mut().unwrap();
                    transport.send(msg).await?;
                }
            }

            // Capture and send frame if interval elapsed
            if last_frame_time.elapsed() >= frame_interval {
                last_frame_time = Instant::now();

                if let Some(frame) = capturer.capture()? {
                    let encoded = encoder.encode(&frame)?;

                    // Skip empty frames (no changes)
                    if encoded.size > 0 {
                        let msg = Message {
                            payload: Some(message::Payload::VideoFrame(encoded.frame)),
                        };
                        let transport = self.transport.as_mut().unwrap();
                        transport.send(msg).await?;
                    }
                }
            }

            // Small sleep to prevent busy loop
            tokio::time::sleep(Duration::from_millis(1)).await;

            // Check if still connected
            if let Some(transport) = self.transport.as_ref() {
                if !transport.is_connected() {
                    tracing::warn!("Connection lost");
                    break;
                }
            } else {
                tracing::warn!("Transport is None");
                break;
            }
        }

        self.state = SessionState::Disconnected;
        Ok(())
    }

    /// Handle incoming message from server
    fn handle_message(&mut self, input: &mut InputController, msg: Message) -> Result<()> {
        match msg.payload {
            Some(message::Payload::MouseEvent(mouse)) => {
                // Handle mouse event
                use crate::proto::MouseEventType;
                use crate::input::MouseButton;

                match MouseEventType::try_from(mouse.event_type).unwrap_or(MouseEventType::MouseMove) {
                    MouseEventType::MouseMove => {
                        input.mouse_move(mouse.x, mouse.y)?;
                    }
                    MouseEventType::MouseDown => {
                        input.mouse_move(mouse.x, mouse.y)?;
                        if let Some(ref buttons) = mouse.buttons {
                            if buttons.left { input.mouse_click(MouseButton::Left, true)?; }
                            if buttons.right { input.mouse_click(MouseButton::Right, true)?; }
                            if buttons.middle { input.mouse_click(MouseButton::Middle, true)?; }
                        }
                    }
                    MouseEventType::MouseUp => {
                        if let Some(ref buttons) = mouse.buttons {
                            if buttons.left { input.mouse_click(MouseButton::Left, false)?; }
                            if buttons.right { input.mouse_click(MouseButton::Right, false)?; }
                            if buttons.middle { input.mouse_click(MouseButton::Middle, false)?; }
                        }
                    }
                    MouseEventType::MouseWheel => {
                        input.mouse_scroll(mouse.wheel_delta_x, mouse.wheel_delta_y)?;
                    }
                }
            }

            Some(message::Payload::KeyEvent(key)) => {
                // Handle keyboard event
                input.key_event(key.vk_code as u16, key.down)?;
            }

            Some(message::Payload::SpecialKey(special)) => {
                use crate::proto::SpecialKey;
                match SpecialKey::try_from(special.key).ok() {
                    Some(SpecialKey::CtrlAltDel) => {
                        input.send_ctrl_alt_del()?;
                    }
                    _ => {}
                }
            }

            Some(message::Payload::Heartbeat(_)) => {
                // Respond to heartbeat
                // TODO: Send heartbeat ack
            }

            Some(message::Payload::Disconnect(disc)) => {
                tracing::info!("Disconnect requested: {}", disc.reason);
                // Check if this is a cancellation (support session)
                if disc.reason.contains("cancelled") {
                    return Err(anyhow::anyhow!("SESSION_CANCELLED: {}", disc.reason));
                }
                // Check if this is an admin disconnect (persistent session)
                if disc.reason.contains("administrator") || disc.reason.contains("Disconnected") {
                    return Err(anyhow::anyhow!("ADMIN_DISCONNECT: {}", disc.reason));
                }
                return Err(anyhow::anyhow!("Disconnect: {}", disc.reason));
            }

            _ => {
                // Ignore unknown messages
            }
        }

        Ok(())
    }
}

+                        TrayAction::ShowDebugWindow => {

+                            // Handled by caller


Actually, I need to handle it properly. The session loop processes tray events but doesn't return the action - it just handles it internally. Let me think about this differently.The easiest approach would be to call show_debug_console directly in the session loop when ShowDebugWindow is detected. But show_debug_console is in main.rs, not session/mod.rs.

Options:
1. Move show_debug_console to a utility module
2. Make show_debug_console public and call it from session
3. Handle it in the session loop directly by importing the Windows API

Let me just make the function callable from the session module by putting the console window logic there, or having session return the action and main handles it.

Actually, the simplest solution is to just call the Windows API directly in the session module when ShowDebugWindow is triggered. Let me do that.


Let me handle the debug window directly in the session module:


                        TrayAction::ShowDebugWindow => {
                            // Handled by caller
                        }

                        TrayAction::ShowDebugWindow => {
                            show_debug_console();
                        }


The file C:\Users\MikeSwanson\Claude\guru-connect\agent\src\session\mod.rs has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
   194→                        TrayAction::ShowDetails => {
   195→                            // TODO: Show a details dialog
   196→                            tracing::info!("User requested details (not yet implemented)");
   197→                        }
   198→                        TrayAction::ShowDebugWindow => {
   199→                            show_debug_console();
   200→                        }
   201→                    }
   202→                }
   203→
   204→                // Check if exit was requested

                        TrayAction::ShowDebugWindow => {
                            show_debug_console();
                        }

//! Session management for the agent
//!
//! Handles the lifecycle of a remote session including:
//! - Connection to server
//! - Authentication
//! - Frame capture and encoding loop
//! - Input event handling

use crate::capture::{self, Capturer, Display};
use crate::chat::{ChatController, ChatMessage as ChatMsg};
use crate::config::Config;
use crate::encoder::{self, Encoder};
use crate::input::InputController;
use crate::proto::{Message, message, ChatMessage};
use crate::transport::WebSocketTransport;
use crate::tray::{TrayController, TrayAction};
use anyhow::Result;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::mpsc;

/// Session manager handles the remote control session
pub struct SessionManager {
    config: Config,
    transport: Option<WebSocketTransport>,
    state: SessionState,
}

#[derive(Debug, Clone, PartialEq)]
enum SessionState {
    Disconnected,
    Connecting,
    Connected,
    Active,
}

impl SessionManager {
    /// Create a new session manager
    pub fn new(config: Config) -> Self {
        Self {
            config,
            transport: None,
            state: SessionState::Disconnected,
        }
    }

    /// Connect to the server
    pub async fn connect(&mut self) -> Result<()> {
        self.state = SessionState::Connecting;

        let hostname = self.config.hostname();
        let transport = WebSocketTransport::connect(
            &self.config.server_url,
            &self.config.agent_id,
            &self.config.api_key,
            Some(&hostname),
            self.config.support_code.as_deref(),
        ).await?;

        self.transport = Some(transport);
        self.state = SessionState::Connected;

        Ok(())
    }

    /// Run the session main loop
    pub async fn run(&mut self) -> Result<()> {
        if self.transport.is_none() {
            anyhow::bail!("Not connected");
        }

        self.state = SessionState::Active;

        // Get primary display
        let primary_display = capture::primary_display()?;
        tracing::info!("Using display: {} ({}x{})", primary_display.name, primary_display.width, primary_display.height);

        // Create capturer
        let mut capturer = capture::create_capturer(
            primary_display.clone(),
            self.config.capture.use_dxgi,
            self.config.capture.gdi_fallback,
        )?;

        // Create encoder
        let mut encoder = encoder::create_encoder(
            &self.config.encoding.codec,
            self.config.encoding.quality,
        )?;

        // Create input controller
        let mut input = InputController::new()?;

        // Calculate frame interval
        let frame_interval = Duration::from_millis(1000 / self.config.capture.fps as u64);
        let mut last_frame_time = Instant::now();

        // Main loop
        loop {
            // Check for incoming messages (non-blocking)
            // Collect messages first, then release borrow before handling
            let messages: Vec<Message> = {
                let transport = self.transport.as_mut().unwrap();
                let mut msgs = Vec::new();
                while let Some(msg) = transport.try_recv()? {
                    msgs.push(msg);
                }
                msgs
            };

            for msg in messages {
                self.handle_message(&mut input, msg)?;
            }

            // Capture and send frame if interval elapsed
            if last_frame_time.elapsed() >= frame_interval {
                last_frame_time = Instant::now();

                if let Some(frame) = capturer.capture()? {
                    let encoded = encoder.encode(&frame)?;

                    // Skip empty frames (no changes)
                    if encoded.size > 0 {
                        let msg = Message {
                            payload: Some(message::Payload::VideoFrame(encoded.frame)),
                        };
                        let transport = self.transport.as_mut().unwrap();
                        transport.send(msg).await?;
                    }
                }
            }

            // Small sleep to prevent busy loop
            tokio::time::sleep(Duration::from_millis(1)).await;

            // Check if still connected
            if let Some(transport) = self.transport.as_ref() {
                if !transport.is_connected() {
                    tracing::warn!("Connection lost");
                    break;
                }
            } else {
                tracing::warn!("Transport is None");
                break;
            }
        }

        self.state = SessionState::Disconnected;
        Ok(())
    }

    /// Run the session main loop with tray and chat event processing
    pub async fn run_with_tray(&mut self, tray: Option<&TrayController>, chat: Option<&ChatController>) -> Result<()> {
        if self.transport.is_none() {
            anyhow::bail!("Not connected");
        }

        self.state = SessionState::Active;

        // Get primary display
        let primary_display = capture::primary_display()?;
        tracing::info!("Using display: {} ({}x{})", primary_display.name, primary_display.width, primary_display.height);

        // Create capturer
        let mut capturer = capture::create_capturer(
            primary_display.clone(),
            self.config.capture.use_dxgi,
            self.config.capture.gdi_fallback,
        )?;

        // Create encoder
        let mut encoder = encoder::create_encoder(
            &self.config.encoding.codec,
            self.config.encoding.quality,
        )?;

        // Create input controller
        let mut input = InputController::new()?;

        // Calculate frame interval
        let frame_interval = Duration::from_millis(1000 / self.config.capture.fps as u64);
        let mut last_frame_time = Instant::now();

        // Main loop
        loop {
            // Process tray events
            if let Some(t) = tray {
                if let Some(action) = t.process_events() {
                    match action {
                        TrayAction::EndSession => {
                            tracing::info!("User requested session end via tray");
                            return Err(anyhow::anyhow!("USER_EXIT: Session ended by user"));
                        }
                        TrayAction::ShowDetails => {
                            // TODO: Show a details dialog
                            tracing::info!("User requested details (not yet implemented)");
                        }
                        TrayAction::ShowDebugWindow => {
                            // Handled by caller
                        }
                    }
                }

                // Check if exit was requested
                if t.exit_requested() {
                    tracing::info!("Exit requested via tray");
                    return Err(anyhow::anyhow!("USER_EXIT: Exit requested by user"));
                }
            }

            // Check for incoming messages (non-blocking)
            let messages: Vec<Message> = {
                let transport = self.transport.as_mut().unwrap();
                let mut msgs = Vec::new();
                while let Some(msg) = transport.try_recv()? {
                    msgs.push(msg);
                }
                msgs
            };

            for msg in messages {
                // Handle chat messages specially
                if let Some(message::Payload::ChatMessage(chat_msg)) = &msg.payload {
                    if let Some(c) = chat {
                        c.add_message(ChatMsg {
                            id: chat_msg.id.clone(),
                            sender: chat_msg.sender.clone(),
                            content: chat_msg.content.clone(),
                            timestamp: chat_msg.timestamp,
                        });
                    }
                    continue; // Don't pass to handle_message
                }
                self.handle_message(&mut input, msg)?;
            }

            // Check for outgoing chat messages from user
            if let Some(c) = chat {
                if let Some(outgoing) = c.poll_outgoing() {
                    let chat_proto = ChatMessage {
                        id: outgoing.id,
                        sender: "client".to_string(),
                        content: outgoing.content,
                        timestamp: outgoing.timestamp,
                    };
                    let msg = Message {
                        payload: Some(message::Payload::ChatMessage(chat_proto)),
                    };
                    let transport = self.transport.as_mut().unwrap();
                    transport.send(msg).await?;
                }
            }

            // Capture and send frame if interval elapsed
            if last_frame_time.elapsed() >= frame_interval {
                last_frame_time = Instant::now();

                if let Some(frame) = capturer.capture()? {
                    let encoded = encoder.encode(&frame)?;

                    // Skip empty frames (no changes)
                    if encoded.size > 0 {
                        let msg = Message {
                            payload: Some(message::Payload::VideoFrame(encoded.frame)),
                        };
                        let transport = self.transport.as_mut().unwrap();
                        transport.send(msg).await?;
                    }
                }
            }

            // Small sleep to prevent busy loop
            tokio::time::sleep(Duration::from_millis(1)).await;

            // Check if still connected
            if let Some(transport) = self.transport.as_ref() {
                if !transport.is_connected() {
                    tracing::warn!("Connection lost");
                    break;
                }
            } else {
                tracing::warn!("Transport is None");
                break;
            }
        }

        self.state = SessionState::Disconnected;
        Ok(())
    }

    /// Handle incoming message from server
    fn handle_message(&mut self, input: &mut InputController, msg: Message) -> Result<()> {
        match msg.payload {
            Some(message::Payload::MouseEvent(mouse)) => {
                // Handle mouse event
                use crate::proto::MouseEventType;
                use crate::input::MouseButton;

                match MouseEventType::try_from(mouse.event_type).unwrap_or(MouseEventType::MouseMove) {
                    MouseEventType::MouseMove => {
                        input.mouse_move(mouse.x, mouse.y)?;
                    }
                    MouseEventType::MouseDown => {
                        input.mouse_move(mouse.x, mouse.y)?;
                        if let Some(ref buttons) = mouse.buttons {
                            if buttons.left { input.mouse_click(MouseButton::Left, true)?; }
                            if buttons.right { input.mouse_click(MouseButton::Right, true)?; }
                            if buttons.middle { input.mouse_click(MouseButton::Middle, true)?; }
                        }
                    }
                    MouseEventType::MouseUp => {
                        if let Some(ref buttons) = mouse.buttons {
                            if buttons.left { input.mouse_click(MouseButton::Left, false)?; }
                            if buttons.right { input.mouse_click(MouseButton::Right, false)?; }
                            if buttons.middle { input.mouse_click(MouseButton::Middle, false)?; }
                        }
                    }
                    MouseEventType::MouseWheel => {
                        input.mouse_scroll(mouse.wheel_delta_x, mouse.wheel_delta_y)?;
                    }
                }
            }

            Some(message::Payload::KeyEvent(key)) => {
                // Handle keyboard event
                input.key_event(key.vk_code as u16, key.down)?;
            }

            Some(message::Payload::SpecialKey(special)) => {
                use crate::proto::SpecialKey;
                match SpecialKey::try_from(special.key).ok() {
                    Some(SpecialKey::CtrlAltDel) => {
                        input.send_ctrl_alt_del()?;
                    }
                    _ => {}
                }
            }

            Some(message::Payload::Heartbeat(_)) => {
                // Respond to heartbeat
                // TODO: Send heartbeat ack
            }

            Some(message::Payload::Disconnect(disc)) => {
                tracing::info!("Disconnect requested: {}", disc.reason);
                // Check if this is a cancellation (support session)
                if disc.reason.contains("cancelled") {
                    return Err(anyhow::anyhow!("SESSION_CANCELLED: {}", disc.reason));
                }
                // Check if this is an admin disconnect (persistent session)
                if disc.reason.contains("administrator") || disc.reason.contains("Disconnected") {
                    return Err(anyhow::anyhow!("ADMIN_DISCONNECT: {}", disc.reason));
                }
                return Err(anyhow::anyhow!("Disconnect: {}", disc.reason));
            }

            _ => {
                // Ignore unknown messages
            }
        }

        Ok(())
    }
}

-                            // Handled by caller

+                            show_debug_console();


Now I need to add the show_debug_console function to session/mod.rs or import it. Let me add it directly to session/mod.rs since it's Windows-specific.


Now add the show_debug_console function to session/mod.rs:


//! Session management for the agent
//!
//! Handles the lifecycle of a remote session including:
//! - Connection to server
//! - Authentication
//! - Frame capture and encoding loop
//! - Input event handling

use crate::capture::{self, Capturer, Display};

//! Session management for the agent
//!
//! Handles the lifecycle of a remote session including:
//! - Connection to server
//! - Authentication
//! - Frame capture and encoding loop
//! - Input event handling

#[cfg(windows)]
use windows::Win32::System::Console::{AllocConsole, GetConsoleWindow};
#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_SHOW};

use crate::capture::{self, Capturer, Display};


The file C:\Users\MikeSwanson\Claude\guru-connect\agent\src\session\mod.rs has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
     1→//! Session management for the agent
     2→//!
     3→//! Handles the lifecycle of a remote session including:
     4→//! - Connection to server
     5→//! - Authentication
     6→//! - Frame capture and encoding loop
     7→//! - Input event handling
     8→
     9→#[cfg(windows)]
    10→use windows::Win32::System::Console::{AllocConsole, GetConsoleWindow};
    11→#[cfg(windows)]
    12→use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_SHOW};
    13→
    14→use crate::capture::{self, Capturer, Display};
    15→use crate::chat::{ChatController, ChatMessage as ChatMsg};
    16→use crate::config::Config;
    17→use crate::encoder::{self, Encoder};
    18→use crate::input::InputController;

//! Session management for the agent
//!
//! Handles the lifecycle of a remote session including:
//! - Connection to server
//! - Authentication
//! - Frame capture and encoding loop
//! - Input event handling

#[cfg(windows)]
use windows::Win32::System::Console::{AllocConsole, GetConsoleWindow};
#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_SHOW};

use crate::capture::{self, Capturer, Display};

//! Session management for the agent
//!
//! Handles the lifecycle of a remote session including:
//! - Connection to server
//! - Authentication
//! - Frame capture and encoding loop
//! - Input event handling

use crate::capture::{self, Capturer, Display};
use crate::chat::{ChatController, ChatMessage as ChatMsg};
use crate::config::Config;
use crate::encoder::{self, Encoder};
use crate::input::InputController;
use crate::proto::{Message, message, ChatMessage};
use crate::transport::WebSocketTransport;
use crate::tray::{TrayController, TrayAction};
use anyhow::Result;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::mpsc;

/// Session manager handles the remote control session
pub struct SessionManager {
    config: Config,
    transport: Option<WebSocketTransport>,
    state: SessionState,
}

#[derive(Debug, Clone, PartialEq)]
enum SessionState {
    Disconnected,
    Connecting,
    Connected,
    Active,
}

impl SessionManager {
    /// Create a new session manager
    pub fn new(config: Config) -> Self {
        Self {
            config,
            transport: None,
            state: SessionState::Disconnected,
        }
    }

    /// Connect to the server
    pub async fn connect(&mut self) -> Result<()> {
        self.state = SessionState::Connecting;

        let hostname = self.config.hostname();
        let transport = WebSocketTransport::connect(
            &self.config.server_url,
            &self.config.agent_id,
            &self.config.api_key,
            Some(&hostname),
            self.config.support_code.as_deref(),
        ).await?;

        self.transport = Some(transport);
        self.state = SessionState::Connected;

        Ok(())
    }

    /// Run the session main loop
    pub async fn run(&mut self) -> Result<()> {
        if self.transport.is_none() {
            anyhow::bail!("Not connected");
        }

        self.state = SessionState::Active;

        // Get primary display
        let primary_display = capture::primary_display()?;
        tracing::info!("Using display: {} ({}x{})", primary_display.name, primary_display.width, primary_display.height);

        // Create capturer
        let mut capturer = capture::create_capturer(
            primary_display.clone(),
            self.config.capture.use_dxgi,
            self.config.capture.gdi_fallback,
        )?;

        // Create encoder
        let mut encoder = encoder::create_encoder(
            &self.config.encoding.codec,
            self.config.encoding.quality,
        )?;

        // Create input controller
        let mut input = InputController::new()?;

        // Calculate frame interval
        let frame_interval = Duration::from_millis(1000 / self.config.capture.fps as u64);
        let mut last_frame_time = Instant::now();

        // Main loop
        loop {
            // Check for incoming messages (non-blocking)
            // Collect messages first, then release borrow before handling
            let messages: Vec<Message> = {
                let transport = self.transport.as_mut().unwrap();
                let mut msgs = Vec::new();
                while let Some(msg) = transport.try_recv()? {
                    msgs.push(msg);
                }
                msgs
            };

            for msg in messages {
                self.handle_message(&mut input, msg)?;
            }

            // Capture and send frame if interval elapsed
            if last_frame_time.elapsed() >= frame_interval {
                last_frame_time = Instant::now();

                if let Some(frame) = capturer.capture()? {
                    let encoded = encoder.encode(&frame)?;

                    // Skip empty frames (no changes)
                    if encoded.size > 0 {
                        let msg = Message {
                            payload: Some(message::Payload::VideoFrame(encoded.frame)),
                        };
                        let transport = self.transport.as_mut().unwrap();
                        transport.send(msg).await?;
                    }
                }
            }

            // Small sleep to prevent busy loop
            tokio::time::sleep(Duration::from_millis(1)).await;

            // Check if still connected
            if let Some(transport) = self.transport.as_ref() {
                if !transport.is_connected() {
                    tracing::warn!("Connection lost");
                    break;
                }
            } else {
                tracing::warn!("Transport is None");
                break;
            }
        }

        self.state = SessionState::Disconnected;
        Ok(())
    }

    /// Run the session main loop with tray and chat event processing
    pub async fn run_with_tray(&mut self, tray: Option<&TrayController>, chat: Option<&ChatController>) -> Result<()> {
        if self.transport.is_none() {
            anyhow::bail!("Not connected");
        }

        self.state = SessionState::Active;

        // Get primary display
        let primary_display = capture::primary_display()?;
        tracing::info!("Using display: {} ({}x{})", primary_display.name, primary_display.width, primary_display.height);

        // Create capturer
        let mut capturer = capture::create_capturer(
            primary_display.clone(),
            self.config.capture.use_dxgi,
            self.config.capture.gdi_fallback,
        )?;

        // Create encoder
        let mut encoder = encoder::create_encoder(
            &self.config.encoding.codec,
            self.config.encoding.quality,
        )?;

        // Create input controller
        let mut input = InputController::new()?;

        // Calculate frame interval
        let frame_interval = Duration::from_millis(1000 / self.config.capture.fps as u64);
        let mut last_frame_time = Instant::now();

        // Main loop
        loop {
            // Process tray events
            if let Some(t) = tray {
                if let Some(action) = t.process_events() {
                    match action {
                        TrayAction::EndSession => {
                            tracing::info!("User requested session end via tray");
                            return Err(anyhow::anyhow!("USER_EXIT: Session ended by user"));
                        }
                        TrayAction::ShowDetails => {
                            // TODO: Show a details dialog
                            tracing::info!("User requested details (not yet implemented)");
                        }
                        TrayAction::ShowDebugWindow => {
                            show_debug_console();
                        }
                    }
                }

                // Check if exit was requested
                if t.exit_requested() {
                    tracing::info!("Exit requested via tray");
                    return Err(anyhow::anyhow!("USER_EXIT: Exit requested by user"));
                }
            }

            // Check for incoming messages (non-blocking)
            let messages: Vec<Message> = {
                let transport = self.transport.as_mut().unwrap();
                let mut msgs = Vec::new();
                while let Some(msg) = transport.try_recv()? {
                    msgs.push(msg);
                }
                msgs
            };

            for msg in messages {
                // Handle chat messages specially
                if let Some(message::Payload::ChatMessage(chat_msg)) = &msg.payload {
                    if let Some(c) = chat {
                        c.add_message(ChatMsg {
                            id: chat_msg.id.clone(),
                            sender: chat_msg.sender.clone(),
                            content: chat_msg.content.clone(),
                            timestamp: chat_msg.timestamp,
                        });
                    }
                    continue; // Don't pass to handle_message
                }
                self.handle_message(&mut input, msg)?;
            }

            // Check for outgoing chat messages from user
            if let Some(c) = chat {
                if let Some(outgoing) = c.poll_outgoing() {
                    let chat_proto = ChatMessage {
                        id: outgoing.id,
                        sender: "client".to_string(),
                        content: outgoing.content,
                        timestamp: outgoing.timestamp,
                    };
                    let msg = Message {
                        payload: Some(message::Payload::ChatMessage(chat_proto)),
                    };
                    let transport = self.transport.as_mut().unwrap();
                    transport.send(msg).await?;
                }
            }

            // Capture and send frame if interval elapsed
            if last_frame_time.elapsed() >= frame_interval {
                last_frame_time = Instant::now();

                if let Some(frame) = capturer.capture()? {
                    let encoded = encoder.encode(&frame)?;

                    // Skip empty frames (no changes)
                    if encoded.size > 0 {
                        let msg = Message {
                            payload: Some(message::Payload::VideoFrame(encoded.frame)),
                        };
                        let transport = self.transport.as_mut().unwrap();
                        transport.send(msg).await?;
                    }
                }
            }

            // Small sleep to prevent busy loop
            tokio::time::sleep(Duration::from_millis(1)).await;

            // Check if still connected
            if let Some(transport) = self.transport.as_ref() {
                if !transport.is_connected() {
                    tracing::warn!("Connection lost");
                    break;
                }
            } else {
                tracing::warn!("Transport is None");
                break;
            }
        }

        self.state = SessionState::Disconnected;
        Ok(())
    }

    /// Handle incoming message from server
    fn handle_message(&mut self, input: &mut InputController, msg: Message) -> Result<()> {
        match msg.payload {
            Some(message::Payload::MouseEvent(mouse)) => {
                // Handle mouse event
                use crate::proto::MouseEventType;
                use crate::input::MouseButton;

                match MouseEventType::try_from(mouse.event_type).unwrap_or(MouseEventType::MouseMove) {
                    MouseEventType::MouseMove => {
                        input.mouse_move(mouse.x, mouse.y)?;
                    }
                    MouseEventType::MouseDown => {
                        input.mouse_move(mouse.x, mouse.y)?;
                        if let Some(ref buttons) = mouse.buttons {
                            if buttons.left { input.mouse_click(MouseButton::Left, true)?; }
                            if buttons.right { input.mouse_click(MouseButton::Right, true)?; }
                            if buttons.middle { input.mouse_click(MouseButton::Middle, true)?; }
                        }
                    }
                    MouseEventType::MouseUp => {
                        if let Some(ref buttons) = mouse.buttons {
                            if buttons.left { input.mouse_click(MouseButton::Left, false)?; }
                            if buttons.right { input.mouse_click(MouseButton::Right, false)?; }
                            if buttons.middle { input.mouse_click(MouseButton::Middle, false)?; }
                        }
                    }
                    MouseEventType::MouseWheel => {
                        input.mouse_scroll(mouse.wheel_delta_x, mouse.wheel_delta_y)?;
                    }
                }
            }

            Some(message::Payload::KeyEvent(key)) => {
                // Handle keyboard event
                input.key_event(key.vk_code as u16, key.down)?;
            }

            Some(message::Payload::SpecialKey(special)) => {
                use crate::proto::SpecialKey;
                match SpecialKey::try_from(special.key).ok() {
                    Some(SpecialKey::CtrlAltDel) => {
                        input.send_ctrl_alt_del()?;
                    }
                    _ => {}
                }
            }

            Some(message::Payload::Heartbeat(_)) => {
                // Respond to heartbeat
                // TODO: Send heartbeat ack
            }

            Some(message::Payload::Disconnect(disc)) => {
                tracing::info!("Disconnect requested: {}", disc.reason);
                // Check if this is a cancellation (support session)
                if disc.reason.contains("cancelled") {
                    return Err(anyhow::anyhow!("SESSION_CANCELLED: {}", disc.reason));
                }
                // Check if this is an admin disconnect (persistent session)
                if disc.reason.contains("administrator") || disc.reason.contains("Disconnected") {
                    return Err(anyhow::anyhow!("ADMIN_DISCONNECT: {}", disc.reason));
                }
                return Err(anyhow::anyhow!("Disconnect: {}", disc.reason));
            }

            _ => {
                // Ignore unknown messages
            }
        }

        Ok(())
    }
}


Now I need to add the show_debug_console function to session/mod.rs.


use crate::capture::{self, Capturer, Display};
use crate::chat::{ChatController, ChatMessage as ChatMsg};
use crate::config::Config;
use crate::encoder::{self, Encoder};
use crate::input::InputController;

use crate::capture::{self, Capturer, Display};
use crate::chat::{ChatController, ChatMessage as ChatMsg};
use crate::config::Config;
use crate::encoder::{self, Encoder};
use crate::input::InputController;

/// Show the debug console window (Windows only)
#[cfg(windows)]
fn show_debug_console() {
    unsafe {
        let hwnd = GetConsoleWindow();
        if hwnd.0 == std::ptr::null_mut() {
            let _ = AllocConsole();
            tracing::info!("Debug console window opened");
        } else {
            let _ = ShowWindow(hwnd, SW_SHOW);
            tracing::info!("Debug console window shown");
        }
    }
}

#[cfg(not(windows))]
fn show_debug_console() {
    // No-op on non-Windows platforms
}