     1→//! System tray icon and menu for the agent
     2→//!
     3→//! Provides a tray icon with menu options:
     4→//! - Connection status
     5→//! - Machine name
     6→//! - End session
     7→
     8→use anyhow::Result;
     9→use muda::{Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu};
    10→use std::sync::atomic::{AtomicBool, Ordering};
    11→use std::sync::Arc;
    12→use tray_icon::{Icon, TrayIcon, TrayIconBuilder, TrayIconEvent};
    13→use tracing::{info, warn};
    14→
    15→#[cfg(windows)]
    16→use windows::Win32::UI::WindowsAndMessaging::{
    17→    PeekMessageW, TranslateMessage, DispatchMessageW, MSG, PM_REMOVE,
    18→};
    19→
    20→/// Events that can be triggered from the tray menu
    21→#[derive(Debug, Clone)]
    22→pub enum TrayAction {
    23→    EndSession,
    24→    ShowDetails,
    25→    // Future: OpenChat,
    26→}
    27→
    28→/// Tray icon controller
    29→pub struct TrayController {
    30→    _tray_icon: TrayIcon,
    31→    menu: Menu,
    32→    end_session_item: MenuItem,
    33→    status_item: MenuItem,
    34→    exit_requested: Arc<AtomicBool>,
    35→}
    36→
    37→impl TrayController {
    38→    /// Create a new tray controller
    39→    pub fn new(machine_name: &str, support_code: Option<&str>) -> Result<Self> {
    40→        // Create menu items
    41→        let status_text = if let Some(code) = support_code {
    42→            format!("Support Session: {}", code)
    43→        } else {
    44→            "Connected".to_string()
    45→        };
    46→
    47→        let status_item = MenuItem::new(&status_text, false, None);
    48→        let machine_item = MenuItem::new(format!("Machine: {}", machine_name), false, None);
    49→        let separator = PredefinedMenuItem::separator();
    50→        let end_session_item = MenuItem::new("End Session", true, None);
    51→
    52→        // Build menu
    53→        let menu = Menu::new();
    54→        menu.append(&status_item)?;
    55→        menu.append(&machine_item)?;
    56→        menu.append(&separator)?;
    57→        menu.append(&end_session_item)?;
    58→
    59→        // Create tray icon
    60→        let icon = create_default_icon()?;
    61→
    62→        let tray_icon = TrayIconBuilder::new()
    63→            .with_menu(Box::new(menu.clone()))
    64→            .with_tooltip(format!("GuruConnect - {}", machine_name))
    65→            .with_icon(icon)
    66→            .build()?;
    67→
    68→        let exit_requested = Arc::new(AtomicBool::new(false));
    69→
    70→        Ok(Self {
    71→            _tray_icon: tray_icon,
    72→            menu,
    73→            end_session_item,
    74→            status_item,
    75→            exit_requested,
    76→        })
    77→    }
    78→
    79→    /// Check if exit has been requested
    80→    pub fn exit_requested(&self) -> bool {
    81→        self.exit_requested.load(Ordering::SeqCst)
    82→    }
    83→
    84→    /// Update the connection status display
    85→    pub fn update_status(&self, status: &str) {
    86→        self.status_item.set_text(status);
    87→    }
    88→
    89→    /// Process pending menu events (call this from the main loop)
    90→    pub fn process_events(&self) -> Option<TrayAction> {
    91→        // Pump Windows message queue to process tray icon events
    92→        #[cfg(windows)]
    93→        pump_windows_messages();
    94→
    95→        // Check for menu events
    96→        if let Ok(event) = MenuEvent::receiver().try_recv() {
    97→            if event.id == self.end_session_item.id() {
    98→                info!("End session requested from tray menu");
    99→                self.exit_requested.store(true, Ordering::SeqCst);
   100→                return Some(TrayAction::EndSession);
   101→            }
   102→        }
   103→
   104→        // Check for tray icon events (like double-click)
   105→        if let Ok(event) = TrayIconEvent::receiver().try_recv() {
   106→            match event {
   107→                TrayIconEvent::DoubleClick { .. } => {
   108→                    info!("Tray icon double-clicked");
   109→                    return Some(TrayAction::ShowDetails);
   110→                }
   111→                _ => {}
   112→            }
   113→        }
   114→
   115→        None
   116→    }
   117→}
   118→
   119→/// Pump the Windows message queue to process tray icon events
   120→#[cfg(windows)]
   121→fn pump_windows_messages() {
   122→    unsafe {
   123→        let mut msg = MSG::default();
   124→        // Process all pending messages
   125→        while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
   126→            let _ = TranslateMessage(&msg);
   127→            DispatchMessageW(&msg);
   128→        }
   129→    }
   130→}
   131→
   132→/// Create a simple default icon (green circle for connected)
   133→fn create_default_icon() -> Result<Icon> {
   134→    // Create a simple 32x32 green icon
   135→    let size = 32u32;
   136→    let mut rgba = vec![0u8; (size * size * 4) as usize];
   137→
   138→    let center = size as f32 / 2.0;
   139→    let radius = size as f32 / 2.0 - 2.0;
   140→
   141→    for y in 0..size {
   142→        for x in 0..size {
   143→            let dx = x as f32 - center;
   144→            let dy = y as f32 - center;
   145→            let dist = (dx * dx + dy * dy).sqrt();
   146→
   147→            let idx = ((y * size + x) * 4) as usize;
   148→
   149→            if dist <= radius {
   150→                // Green circle
   151→                rgba[idx] = 76;      // R
   152→                rgba[idx + 1] = 175; // G
   153→                rgba[idx + 2] = 80;  // B
   154→                rgba[idx + 3] = 255; // A
   155→            } else if dist <= radius + 1.0 {
   156→                // Anti-aliased edge
   157→                let alpha = ((radius + 1.0 - dist) * 255.0) as u8;
   158→                rgba[idx] = 76;
   159→                rgba[idx + 1] = 175;
   160→                rgba[idx + 2] = 80;
   161→                rgba[idx + 3] = alpha;
   162→            }
   163→        }
   164→    }
   165→
   166→    let icon = Icon::from_rgba(rgba, size, size)?;
   167→    Ok(icon)
   168→}
   169→
   170→#[cfg(test)]
   171→mod tests {
   172→    use super::*;
   173→
   174→    #[test]
   175→    fn test_create_icon() {
   176→        let icon = create_default_icon();
   177→        assert!(icon.is_ok());
   178→    }
   179→}
   180→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//! 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,
    // Future: OpenChat,
}

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

impl TrayController {
    /// Create a new tray controller
    pub fn new(machine_name: &str, support_code: Option<&str>) -> Result<Self> {
        // Create menu items
        let status_text = if let Some(code) = support_code {
            format!("Support Session: {}", code)
        } else {
            "Connected".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();
        let end_session_item = MenuItem::new("End Session", true, 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());
    }
}


I need to:
1. Modify TrayController::new() to accept a flag for whether "End Session" should be available
2. Only show "End Session" menu item for support sessions3. Update main.rs to pass this flag and not check exit_requested for persistent agentsLet me update the tray controller first.


Edit

impl TrayController {
    /// Create a new tray controller
    pub fn new(machine_name: &str, support_code: Option<&str>) -> Result<Self> {
        // Create menu items
        let status_text = if let Some(code) = support_code {
            format!("Support Session: {}", code)
        } else {
            "Connected".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();
        let end_session_item = MenuItem::new("End Session", true, None);

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

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)?;


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:
    33→    status_item: MenuItem,
    34→    exit_requested: Arc<AtomicBool>,
    35→}
    36→
    37→impl TrayController {
    38→    /// Create a new tray controller
    39→    /// `allow_end_session` - If true, show "End Session" menu item (only for support sessions)
    40→    pub fn new(machine_name: &str, support_code: Option<&str>, allow_end_session: bool) -> Result<Self> {
    41→        // Create menu items
    42→        let status_text = if let Some(code) = support_code {
    43→            format!("Support Session: {}", code)
    44→        } else {
    45→            "Persistent Agent".to_string()
    46→        };
    47→
    48→        let status_item = MenuItem::new(&status_text, false, None);
    49→        let machine_item = MenuItem::new(format!("Machine: {}", machine_name), false, None);
    50→        let separator = PredefinedMenuItem::separator();
    51→
    52→        // Only show "End Session" for support sessions
    53→        // Persistent agents can only be removed by admin
    54→        let end_session_item = if allow_end_session {
    55→            MenuItem::new("End Session", true, None)
    56→        } else {
    57→            MenuItem::new("Managed by Administrator", false, None)
    58→        };
    59→
    60→        // Build menu
    61→        let menu = Menu::new();
    62→        menu.append(&status_item)?;
    63→        menu.append(&machine_item)?;
    64→        menu.append(&separator)?;
    65→        menu.append(&end_session_item)?;
    66→
    67→        // Create tray icon
    68→        let icon = create_default_icon()?;
    69→

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)?;

//! 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,
    // Future: OpenChat,
}

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

impl TrayController {
    /// Create a new tray controller
    pub fn new(machine_name: &str, support_code: Option<&str>) -> Result<Self> {
        // Create menu items
        let status_text = if let Some(code) = support_code {
            format!("Support Session: {}", code)
        } else {
            "Connected".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();
        let end_session_item = MenuItem::new("End Session", true, 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());
    }
}

-    pub fn new(machine_name: &str, support_code: Option<&str>) -> Result<Self> {

+    /// `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> {

+            "Persistent Agent".to_string()

-        let end_session_item = MenuItem::new("End Session", true, None);

+        // 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)

+            MenuItem::new("Managed by Administrator", false, None)


Now I need to update main.rs to:
1. Pass `is_support_session` to TrayController::new() for the `allow_end_session` flag2. Remove the exit_requested checks for persistent agents (they should only exit on ADMIN_DISCONNECT)


Now update main.rs to pass the flag and remove exit checks for persistent agents:


    // Create tray icon
    let tray = match tray::TrayController::new(&hostname, config.support_code.as_deref()) {
        Ok(t) => {
            info!("Tray icon created");
            Some(t)
        }
        Err(e) => {
            warn!("Failed to create tray icon: {}. Continuing without tray.", e);
            None
        }
    };

    // 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
        }
    };


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:
   185→    if let Err(e) = startup::add_to_startup() {
   186→        warn!("Failed to add to startup: {}. Agent won't persist through reboot.", e);
   187→    }
   188→
   189→    // Create tray icon
   190→    // Only support sessions can be ended by user - persistent agents are admin-managed
   191→    let tray = match tray::TrayController::new(&hostname, config.support_code.as_deref(), is_support_session) {
   192→        Ok(t) => {
   193→            info!("Tray icon created");
   194→            Some(t)
   195→        }
   196→        Err(e) => {
   197→            warn!("Failed to create tray icon: {}. Continuing without tray.", e);
   198→            None
   199→        }
   200→    };
   201→
   202→    // Create chat controller
   203→    let chat_ctrl = chat::ChatController::new();
   204→    if chat_ctrl.is_some() {

    // 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
        }
    };

//! 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.

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
    let tray = match tray::TrayController::new(&hostname, config.support_code.as_deref()) {
        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
        if let Some(ref t) = tray {
            if t.exit_requested() {
                info!("Exit requested by user");
                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(());
        }

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

        // Wait before reconnecting (only for persistent agent connections)
        info!("Reconnecting in 5 seconds...");
        tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
    }
}

-    let tray = match tray::TrayController::new(&hostname, config.support_code.as_deref()) {

+    // 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) {


Now I need to update the exit_requested checks. For persistent agents, we shouldn't check exit_requested since they can't end the session. But I need to be careful here - the tray still exists for persistent agents (for status display), it just doesn't have an active "End Session" button.

Looking at the current code flow:
1. Before connecting: checks exit_requested -> should only apply to support sessions
2. After USER_EXIT error: should only be triggered by support sessions now
3. After session ends and before reconnect: checks exit_requested -> should only apply to support sessions

Let me update the checks to only apply to support sessions.


Now let me update the exit checks to only apply to support sessions:


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

        // 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(());
                }
            }
        }


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:
   208→    // Connect to server and run main loop
   209→    loop {
   210→        info!("Connecting to server...");
   211→
   212→        // Check if user requested exit via tray before connecting (support sessions only)
   213→        if is_support_session {
   214→            if let Some(ref t) = tray {
   215→                if t.exit_requested() {
   216→                    info!("Exit requested by user");
   217→                    cleanup_on_exit();
   218→                    return Ok(());
   219→                }
   220→            }
   221→        }
   222→
   223→        match session.connect().await {
   224→            Ok(_) => {
   225→                info!("Connected 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(());
                }
            }
        }

//! 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.

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
        if let Some(ref t) = tray {
            if t.exit_requested() {
                info!("Exit requested by user");
                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(());
        }

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

        // Wait before reconnecting (only for persistent agent connections)
        info!("Reconnecting in 5 seconds...");
        tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
    }
}

-        // Check if user requested exit via tray before connecting

-                info!("Exit requested by user");

+        // Check if user requested exit via tray before connecting (support sessions only)

+            if let Some(ref t) = tray {

+                if t.exit_requested() {

+                    info!("Exit requested by user");


Now let me update the exit check at the end of the loop (before the reconnect delay).


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

        // Wait before reconnecting (only for persistent agent connections)

// Wait before reconnecting (persistent agents only - support sessions already exited above)


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:
   293→            cleanup_on_exit();
   294→            return Ok(());
   295→        }
   296→
   297→        // Wait before reconnecting (persistent agents only - support sessions already exited above)
   298→        info!("Reconnecting in 5 seconds...");
   299→        tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
   300→    }
   301→}

//! 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.

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(());
        }

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

        // Wait before reconnecting (only for persistent agent connections)
        info!("Reconnecting in 5 seconds...");
        tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
    }
}

-        // Check if user requested exit via tray

-        // Wait before reconnecting (only for persistent agent connections)

+        // Wait before reconnecting (persistent agents only - support sessions already exited above)


Now let me commit and deploy these changes.


Now let me commit and deploy:


Bash

cd /c/Users/MikeSwanson/Claude/guru-connect && git add -A && git commit -m "$(cat <<'EOF'
Restrict session termination to support sessions only

- Persistent agents: No "End Session" menu, shows "Managed by Administrator"
- Persistent agents: Always reconnect, can only be removed via admin disconnect
- Support sessions: User can end via tray icon
- Tray icon still shows for persistent agents (status display only)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
EOF
)"

Commit session termination restrictions


[main d7c272d] Restrict session termination to support sessions only
 2 files changed, 22 insertions(+), 19 deletions(-)
warning: in the working copy of 'agent/src/tray/mod.rs', LF will be replaced by CRLF the next time Git touches it


cd /c/Users/MikeSwanson/Claude/guru-connect && git push && ssh guru@172.16.3.30 bash << 'ENDSSH'
export PATH=/usr/bin:/bin
source ~/.cargo/env
cd ~/guru-connect
git pull
cargo build -p guruconnect-agent --release --target x86_64-pc-windows-gnu 2>&1 | tail -3
cp target/x86_64-pc-windows-gnu/release/guruconnect-agent.exe server/static/GuruConnect.exe
echo "Agent deployed: $(ls -la server/static/GuruConnect.exe | awk '{print $5}') bytes"
ENDSSH

Push and deploy updated agent


Updating 52c47b2..d7c272d
Fast-forward
 agent/src/main.rs     | 27 +++++++++++----------------
 agent/src/tray/mod.rs | 14 +++++++++++---
 2 files changed, 22 insertions(+), 19 deletions(-)

warning: `guruconnect-agent` (bin "guruconnect-agent") generated 45 warnings (run `cargo fix --bin "guruconnect-agent" -p guruconnect-agent` to apply 20 suggestions)
    Finished `release` profile [optimized] target(s) in 6.11s
Agent deployed: 8092696 bytes
remote: . Processing 1 references
remote: Processed 1 references in total
To https://git.azcomputerguru.com/azcomputerguru/guru-connect.git
   52c47b2..d7c272d  main -> main
From https://git.azcomputerguru.com/azcomputerguru/guru-connect
   52c47b2..d7c272d  main       -> origin/main


Done. Let me summarize what was changed.


Done. The persistence model is now:

## Persistent Agents (no support code)
- **Startup**: Always added to Windows startup registry
- **Tray menu**: Shows "Managed by Administrator" (disabled, can't click)
- **Disconnect**: Always reconnects automatically
- **Removal**: Only via admin "Disconnect" button in dashboard (triggers uninstall)

## Support Sessions (with 6-digit code)
- **Startup**: Added temporarily while active
- **Tray menu**: Shows "End Session" (user can click to end)
- **Disconnect**: Does NOT reconnect, cleans up and exits
- **Removal**: User can end, or technician can cancel

So now if Howard runs `GuruConnect.exe` (no code), it will:
1. Add itself to startup
2. Connect to server
3. Show tray icon with "Managed by Administrator"
4. If disconnected (network, server restart), wait 5 seconds and reconnect
5. If machine reboots, auto-start and reconnect
6. Only removable via dashboard Disconnect button


Now I'd like there to be no window open when run.  Only the tray is visible, for now add a "Debug Window" to tray which will show that window.


The user wants the agent to run without a console window visible by default. Only the tray icon should be visible. They want a "Debug Window" menu item in the tray that can show the console/log window when needed.

For Windows, this means:
1. Changing the application to be a Windows subsystem app (no console) instead of a console app
2. Adding a "Debug Window" menu item to the tray
3. When clicked, show a window with logs/debug info

The simplest approach for a Rust Windows app is to use `#![windows_subsystem = "windows"]` to hide the console window. Then for debug, we can either:
1. Allocate a console when "Debug Window" is clicked
2. Create a custom window that shows logs

Option 1 is simpler - use AllocConsole() to create a console window on demand.

Let me implement this:

1. Add `#![windows_subsystem = "windows"]` to main.rs to hide console by default
2. Add "Debug Window" menu item to tray
3. When clicked, call AllocConsole() to show a console window with logs


I'll make the agent run without a console window and add a "Debug Window" option to the tray.


Edit

//! 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.

mod capture;

//! 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;


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:
     1→//! GuruConnect Agent - Remote Desktop Agent for Windows
     2→//!
     3→//! Provides screen capture, input injection, and remote control capabilities.
     4→//!
     5→//! Usage:
     6→//!   guruconnect-agent.exe [support_code]
     7→//!
     8→//! If a support code is provided, the agent will connect using that code
     9→//! for a one-time support session.
    10→
    11→// Hide console window by default on Windows (release builds)
    12→#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
    13→
    14→mod capture;
    15→mod chat;
    16→mod config;
    17→mod encoder;
    18→mod input;

//! 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;

//! 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.

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;
    }
}

+// Hide console window by default on Windows (release builds)

+#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]


Now I need to:
1. Add "Show Debug Window" menu item to the tray
2. Add a function to allocate/show a console window
3. Add the TrayAction for itLet me update the tray module.


Now let me update the tray to add the "Debug Window" option:


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

/// Events that can be triggered from the tray menu
#[derive(Debug, Clone)]
pub enum TrayAction {
    EndSession,
    ShowDetails,
    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:
    16→use windows::Win32::UI::WindowsAndMessaging::{
    17→    PeekMessageW, TranslateMessage, DispatchMessageW, MSG, PM_REMOVE,
    18→};
    19→
    20→/// Events that can be triggered from the tray menu
    21→#[derive(Debug, Clone)]
    22→pub enum TrayAction {
    23→    EndSession,
    24→    ShowDetails,
    25→    ShowDebugWindow,
    26→}
    27→
    28→/// Tray icon controller
    29→pub struct TrayController {
    30→    _tray_icon: TrayIcon,

/// Events that can be triggered from the tray menu
#[derive(Debug, Clone)]
pub enum TrayAction {
    EndSession,
    ShowDetails,
    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,
    // Future: OpenChat,
}

/// Tray icon controller
pub struct TrayController {
    _tray_icon: TrayIcon,
    menu: Menu,
    end_session_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());
    }
}

pub enum TrayAction {
EndSession,
ShowDetails,
-    // Future: OpenChat,
+    ShowDebugWindow,
}
/// Tray icon controller


Now I need to add the debug_item field to TrayController and update the new() function to add the menu item.


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

/// 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>,
}


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:
    24→    ShowDetails,
    25→    ShowDebugWindow,
    26→}
    27→
    28→/// Tray icon controller
    29→pub struct TrayController {
    30→    _tray_icon: TrayIcon,
    31→    menu: Menu,
    32→    end_session_item: MenuItem,
    33→    debug_item: MenuItem,
    34→    status_item: MenuItem,
    35→    exit_requested: Arc<AtomicBool>,
    36→}
    37→
    38→impl TrayController {
    39→    /// Create a new tray controller
    40→    /// `allow_end_session` - If true, show "End Session" menu item (only for support sessions)

/// 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>,
}

//! 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,
    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());
    }
}

_tray_icon: TrayIcon,
menu: Menu,
end_session_item: MenuItem,
+    debug_item: MenuItem,
status_item: MenuItem,
exit_requested: Arc<AtomicBool>,
}


Now I need to update the new() function to create and add the debug menu item.


        // 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)?;

        // 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)?;