// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Diagnostics; using System.Text; namespace AgentLauncher; class Program { private static readonly string LogFilePath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "AgentLauncher", "agent.log"); private static readonly Stopwatch _processStopwatch = Stopwatch.StartNew(); [STAThread] static int Main(string[] args) { try { LogTiming("PROCESS_START", "Process started"); Log("TypeAgent Launcher starting..."); Log($"Arguments: {string.Join(" ", args)}"); LogTiming("ARGS_PARSED", "Arguments parsed"); if (args.Length > 0) { var firstArg = args[0]; // COM server registration removed - using URI protocol activation instead if (firstArg.StartsWith("typeagent-launcher:", StringComparison.OrdinalIgnoreCase)) { LogTiming("PROTOCOL_DETECTED", "Protocol activation detected"); Log($"Protocol activation received: {firstArg}"); var result = HandleProtocolActivation(firstArg).GetAwaiter().GetResult(); LogTiming("PROCESS_COMPLETE", "Process complete"); return result; } else if (firstArg == "--test" || firstArg == "-t") { var prompt = args.Length > 1 ? args[1] : "Hello, agent!"; return TestAgent(prompt).GetAwaiter().GetResult(); } else if (firstArg == "--register" || firstArg == "-r") { return RegisterWithODR(); } else if (firstArg == "--help" || firstArg == "-h") { PrintHelp(); return 0; } } Log("No recognized arguments, exiting silently"); return 0; } catch (Exception ex) { Log($"ERROR: {ex}"); Console.Error.WriteLine($"Error: {ex.Message}"); return 1; } } private static async Task HandleProtocolActivation(string uriString) { try { LogTiming("URI_PARSE_START", "Starting URI parse"); var uri = new Uri(uriString); Log($"Parsing URI: {uri}"); var queryParams = ParseQueryString(uri.Query); LogTiming("URI_PARSE_COMPLETE", "URI parsed"); var agentName = queryParams.GetValueOrDefault("agentName"); var prompt = queryParams.GetValueOrDefault("prompt"); if (string.IsNullOrWhiteSpace(agentName) || string.IsNullOrWhiteSpace(prompt)) { var error = $"Missing required parameters. agentName: {agentName ?? "(null)"}, prompt: {prompt ?? "(null)"}"; Log($"ERROR: {error}"); Console.Error.WriteLine(error); return 1; } Log($"Processing protocol activation - Agent: {agentName}, Prompt: {prompt}"); LogTiming("NODE_CALL_START", "Starting Node.js execution"); var result = await ProcessWithNodeAsync(agentName, prompt, null); LogTiming("NODE_CALL_COMPLETE", "Node.js execution complete"); Log($"Protocol activation completed successfully"); Log($"Result: {result?.Substring(0, Math.Min(100, result?.Length ?? 0))}..."); return 0; } catch (Exception ex) { Log($"ERROR: Protocol activation failed: {ex}"); Console.Error.WriteLine($"Protocol activation error: {ex.Message}"); return 1; } } private static Dictionary ParseQueryString(string query) { var result = new Dictionary(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrWhiteSpace(query)) return result; query = query.TrimStart('?'); var pairs = query.Split('&'); foreach (var pair in pairs) { var parts = pair.Split('=', 2); if (parts.Length == 2) { var key = Uri.UnescapeDataString(parts[0]); var value = Uri.UnescapeDataString(parts[1]); result[key] = value; } } return result; } private static async Task TestAgent(string prompt) { Console.WriteLine($"Testing agent with prompt: {prompt}"); var settings = AgentSettings.Instance; Console.WriteLine($"Script path: {settings.GetResolvedScriptPath()}"); Console.WriteLine(); try { var result = await ProcessWithNodeAsync("TestAgent", prompt, null); Console.WriteLine("Result:"); Console.WriteLine(result); return 0; } catch (Exception ex) { Console.Error.WriteLine($"Test failed: {ex.Message}"); Log($"Test failed: {ex}"); return 1; } } private static int RegisterWithODR() { try { var manifestPath = Path.Combine(AppContext.BaseDirectory, "Assets", "agent-definition.json"); if (!File.Exists(manifestPath)) { Console.Error.WriteLine($"ERROR: agent-definition.json not found at: {manifestPath}"); Log($"ERROR: agent-definition.json not found at: {manifestPath}"); return 1; } Console.WriteLine($"Registering agent with ODR..."); Console.WriteLine($"Manifest path: {manifestPath}"); Log($"Registering agent with ODR using manifest: {manifestPath}"); var processInfo = new ProcessStartInfo { FileName = "odr", Arguments = $"app-agents add \"{manifestPath}\"", CreateNoWindow = false, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true }; using var process = Process.Start(processInfo); if (process == null) { Console.Error.WriteLine("ERROR: Failed to start ODR process"); Log("ERROR: Failed to start ODR process"); return 1; } var output = process.StandardOutput.ReadToEnd(); var error = process.StandardError.ReadToEnd(); process.WaitForExit(); if (!string.IsNullOrWhiteSpace(output)) { Console.WriteLine(output); Log($"ODR output: {output}"); } if (!string.IsNullOrWhiteSpace(error)) { Console.Error.WriteLine(error); Log($"ODR error: {error}"); } if (process.ExitCode == 0) { Console.WriteLine("Agent registered successfully!"); Log("Agent registered successfully with ODR"); return 0; } else { Console.Error.WriteLine($"ODR registration failed with exit code: {process.ExitCode}"); Log($"ODR registration failed with exit code: {process.ExitCode}"); return process.ExitCode; } } catch (Exception ex) { Console.Error.WriteLine($"ERROR: {ex.Message}"); Log($"ERROR during ODR registration: {ex}"); return 1; } } private static void PrintHelp() { Console.WriteLine("TypeAgent Launcher"); Console.WriteLine(); Console.WriteLine("Usage:"); Console.WriteLine(" AgentLauncher.exe [options]"); Console.WriteLine(); Console.WriteLine("Options:"); Console.WriteLine(" --test, -t [prompt] Test agent with a prompt"); Console.WriteLine(" --register, -r Register agent with On-Device Registry"); Console.WriteLine(" --help, -h Show this help"); Console.WriteLine(); Console.WriteLine("Environment Variables:"); Console.WriteLine(" TYPEAGENT_NODE_PATH Path to Node.js executable"); Console.WriteLine(" TYPEAGENT_TIMEOUT Timeout in milliseconds (default: 60000)"); Console.WriteLine(" TYPEAGENT_VERBOSE Enable verbose logging (true/false)"); Console.WriteLine(" TYPEAGENT_WORKDIR Working directory for Node.js"); Console.WriteLine(); Console.WriteLine("Examples:"); Console.WriteLine(" AgentLauncher.exe --test \"Hello, world!\""); Console.WriteLine(" AgentLauncher.exe --register"); Console.WriteLine(); Console.WriteLine(" SET TYPEAGENT_VERBOSE=true"); Console.WriteLine(" SET TYPEAGENT_TIMEOUT=120000"); Console.WriteLine(" AgentLauncher.exe --test \"Long running test\""); } public static async Task ProcessWithNodeAsync( string agentName, string prompt, string? filePath) { LogTiming("SETTINGS_LOAD_START", "Loading settings"); var settings = AgentSettings.Instance; var scriptPath = settings.GetResolvedScriptPath(); var nodePath = settings.GetResolvedNodePath(); LogTiming("SETTINGS_LOAD_COMPLETE", "Settings loaded"); Log($"Processing request - Agent: {agentName}, Prompt: {prompt?.Substring(0, Math.Min(50, prompt?.Length ?? 0))}..."); Log($"Script: {scriptPath}"); Log($"Node: {nodePath}"); if (!File.Exists(scriptPath)) { throw new FileNotFoundException( $"Script not found: {scriptPath}. " + $"The bundled script should be included in the MSIX package."); } var uri = BuildUriString(agentName, prompt, filePath); Log($"URI: {uri}"); LogTiming("PROCESS_SETUP_START", "Setting up Node.js process"); var processInfo = new ProcessStartInfo { FileName = nodePath, Arguments = $"\"{scriptPath}\" \"{uri}\"", CreateNoWindow = true, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, RedirectStandardInput = false, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8 }; if (settings.Environment != null) { foreach (var (key, value) in settings.Environment) { processInfo.Environment[key] = value; } } var workingDir = settings.GetResolvedWorkingDirectory(); if (!string.IsNullOrWhiteSpace(workingDir)) { processInfo.WorkingDirectory = workingDir; } var process = new Process { StartInfo = processInfo }; var outputBuilder = new StringBuilder(); var errorBuilder = new StringBuilder(); process.OutputDataReceived += (sender, e) => { if (e.Data != null) { outputBuilder.AppendLine(e.Data); } }; process.ErrorDataReceived += (sender, e) => { if (e.Data != null) { errorBuilder.AppendLine(e.Data); } }; try { LogTiming("PROCESS_START", "Starting Node.js process"); process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); LogTiming("PROCESS_STARTED", "Process started, waiting for completion"); var completed = await Task.Run(() => process.WaitForExit(settings.TimeoutMs)); LogTiming("PROCESS_WAIT_COMPLETE", "Process wait completed"); if (!completed) { process.Kill(); throw new TimeoutException( $"Script execution timed out after {settings.TimeoutMs}ms. " + $"Increase timeout by setting TYPEAGENT_TIMEOUT environment variable (in milliseconds)."); } var exitCode = process.ExitCode; var output = outputBuilder.ToString().Trim(); var error = errorBuilder.ToString().Trim(); Log($"Exit code: {exitCode}"); LogTiming("OUTPUT_COLLECTED", "Output and error streams collected"); if (settings.VerboseLogging) { Log($"Output: {output}"); Log($"Error: {error}"); } if (exitCode != 0) { throw new Exception($"Script failed with exit code {exitCode}. Error: {error}"); } if (string.IsNullOrWhiteSpace(output)) { throw new Exception("Script produced no output"); } return output; } catch (Exception ex) when (ex is not TimeoutException) { Log($"ERROR: Failed to execute Node.js script: {ex.Message}"); throw new Exception($"Failed to execute script: {ex.Message}", ex); } } private static string BuildUriString(string agentName, string prompt, string? filePath) { var uri = $"type-agent://?request={Uri.EscapeDataString(prompt)}"; if (!string.IsNullOrWhiteSpace(filePath)) { uri += $"&file={Uri.EscapeDataString(filePath)}"; } return uri; } public static void Log(string message) { try { var directory = Path.GetDirectoryName(LogFilePath); if (directory != null && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); var logMessage = $"[{timestamp}] {message}"; File.AppendAllText(LogFilePath, logMessage + Environment.NewLine); } catch { } } private static void LogTiming(string marker, string description) { var elapsed = _processStopwatch.ElapsedMilliseconds; Log($"⏱️ TIMING [{marker}] +{elapsed}ms - {description}"); } }