using Agent.App.Cli; using Agent.App.Configuration; using Agent.App.Logging; using Agent.Core.Localization; using Agent.Core.Prompts; using Agent.Core.Sessions; using Agent.Mcp; using Agent.Core.Runtime; using Agent.Windows.Diagnostics; using Agent.Core.Tools; using Agent.Windows.Recovery; using Agent.Windows.Security; using Agent.Windows.Tools; namespace Agent.App; internal static class Program { public static async Task Main(string[] args) { try { CommandLine commandLine = CommandLine.Parse(args); string configPath = commandLine.ConfigPath is null ? Path.Combine(AppContext.BaseDirectory, "agent.yaml") : Path.GetFullPath(commandLine.ConfigPath); AgentConfiguration configuration = ConfigurationLoader.Load(configPath); if (commandLine.Transport is not null) configuration.Mcp.Transport = commandLine.Transport; ConfigurationLoader.Validate(configuration, Path.GetDirectoryName(configPath)!); string language = commandLine.Language ?? configuration.Application.Language ?? "en"; string baseDirectory = Path.GetDirectoryName(configPath)!; string localeDirectory = ConfigurationLoader.Resolve(baseDirectory, configuration.Localization.Directory); LocaleCatalog locale = LocaleCatalog.Load(localeDirectory, language, configuration.Localization.Fallback); _ = FixedPrompt.Load(); string logDirectory = ConfigurationLoader.Resolve(baseDirectory, configuration.Logging.Directory); UserOnlyDirectory.Ensure(logDirectory); using var log = new RollingFileLog(logDirectory, configuration.Logging.MaxFileBytes, configuration.Logging.RetainedFiles, configuration.Logging.Level); if (commandLine.Command == AppCommand.Check) { WindowsPlatformCheckResult platform = WindowsPlatformCheck.Run(); Console.Out.WriteLine($"administrator={platform.IsAdministrator.ToString().ToLowerInvariant()}"); Console.Out.WriteLine($"bfe_engine_open={platform.BfeEngineOpen.ToString().ToLowerInvariant()}"); Console.Out.WriteLine($"wfp_write_transaction={platform.WfpWriteTransaction.ToString().ToLowerInvariant()}"); Console.Out.WriteLine($"wfp_filter_add_delete={platform.WfpFilterAddDelete.ToString().ToLowerInvariant()}"); if (!platform.Success) { string detail = $"{locale.Get("check.wfp_admin_required")} error_code={platform.ErrorCode}; operation={platform.ErrorOperation}"; string failure = locale.Format("check.failure", new Dictionary { ["message"] = detail }); log.Error(failure); Console.Error.WriteLine(failure); return 2; } log.Information(locale.Get("check.success")); Console.Out.WriteLine(locale.Get("check.success")); return 0; } log.Information(locale.Get("app.starting")); WindowsPlatformCheckResult servePlatform = WindowsPlatformCheck.Run(); if (!servePlatform.Success) { string failure = locale.Format("app.preflight_failed", new Dictionary { ["operation"] = servePlatform.ErrorOperation ?? "privilege/BFE/WFP probe", ["error_code"] = servePlatform.ErrorCode, ["administrator"] = servePlatform.IsAdministrator.ToString().ToLowerInvariant(), ["bfe"] = servePlatform.BfeEngineOpen.ToString().ToLowerInvariant(), ["transaction"] = servePlatform.WfpWriteTransaction.ToString().ToLowerInvariant(), ["filters"] = servePlatform.WfpFilterAddDelete.ToString().ToLowerInvariant() }); log.Fatal(failure); return 2; } var sessionStore = new SessionStore(); InternalToolDispatcher toolDispatcher = CreateToolDispatcher(configuration, baseDirectory, logDirectory, sessionStore); using var runtimeObserver = new JsonLinesRuntimeObserver(logDirectory, configuration.Logging.MaxFileBytes, configuration.Logging.RetainedFiles); string taskStateDirectory = Path.Combine(baseDirectory, "state", "mcp-tasks"); UserOnlyDirectory.Ensure(taskStateDirectory); var taskStoreSettings = new McpTaskStoreSettings(taskStateDirectory, TimeSpan.FromHours(configuration.Mcp.Tasks.TimeToLiveHours), configuration.Mcp.Tasks.PollIntervalMs); using var hostObserver = new McpHostLogObserver(log, locale, logDirectory, configuration.Logging.HttpRequests, configuration.Logging.MaxFileBytes, configuration.Logging.RetainedFiles); if (configuration.Mcp.Transport == "stdio") { string userPrompt = File.ReadAllText(ConfigurationLoader.Resolve(baseDirectory, configuration.Agent.UserSystemPrompt)); string skillDirectory = ConfigurationLoader.Resolve(baseDirectory, configuration.Skills.Directory); var skills = SkillLoader.Load(skillDirectory); AgentRuntimeSettings runtimeSettings = CreateRuntimeSettings(configuration, userPrompt, language, skills, locale); var visionLimits = new VisionLimits(configuration.Mcp.Images.MaxCount, configuration.Mcp.Images.MaxBytesEach, configuration.Mcp.Images.MaxTotalBytes); await McpHost.RunStdioAsync(locale, OllamaSettingsFactory.Create(configuration.Ollama), runtimeSettings, visionLimits, CancellationToken.None, toolDispatcher, runtimeObserver, taskStoreSettings, hostObserver, sessionStore: sessionStore).ConfigureAwait(false); return 0; } if (configuration.Mcp.Transport == "http") { string userPrompt = File.ReadAllText(ConfigurationLoader.Resolve(baseDirectory, configuration.Agent.UserSystemPrompt)); string skillDirectory = ConfigurationLoader.Resolve(baseDirectory, configuration.Skills.Directory); var skills = SkillLoader.Load(skillDirectory); AgentRuntimeSettings runtimeSettings = CreateRuntimeSettings(configuration, userPrompt, language, skills, locale); var visionLimits = new VisionLimits(configuration.Mcp.Images.MaxCount, configuration.Mcp.Images.MaxBytesEach, configuration.Mcp.Images.MaxTotalBytes); await McpHost.RunHttpAsync(locale, OllamaSettingsFactory.Create(configuration.Ollama), runtimeSettings, visionLimits, configuration.Mcp.Http.Host, configuration.Mcp.Http.Port, CancellationToken.None, toolDispatcher, runtimeObserver, taskStoreSettings, hostObserver, sessionStore: sessionStore).ConfigureAwait(false); return 0; } throw new ConfigurationException("Unsupported MCP transport."); } catch (Exception ex) when (ex is CommandLineException or ConfigurationException or LocaleException or IOException or UnauthorizedAccessException) { Console.Error.WriteLine($"agent-mcp: {ex.Message}"); return 2; } catch (Exception ex) when (ex is not (OutOfMemoryException or StackOverflowException or AccessViolationException)) { Console.Error.WriteLine($"agent-mcp: [APP-UNHANDLED] {ex.Message}"); return 2; } } private static InternalToolDispatcher CreateToolDispatcher(AgentConfiguration configuration, string baseDirectory, string logDirectory, SessionStore? sessionStore = null) { string stateDirectory = Path.Combine(baseDirectory, "state"); string journalDirectory = Path.Combine(stateDirectory, "journals"); string backupDirectory = Path.Combine(stateDirectory, "backups"); Directory.CreateDirectory(journalDirectory); Directory.CreateDirectory(backupDirectory); var audit = new JsonLinesSecurityAuditSink(Path.Combine(logDirectory, "audit.jsonl")); RecoveryResult recovery = new RecoveryManager(journalDirectory, audit).RecoverAll(); if (!recovery.IsClean) throw new InvalidOperationException("Recovery did not reach a clean state: " + string.Join(" | ", recovery.Errors)); var fileEntries = new List(); Add(configuration.Sandbox.Filesystem.Read, FileCapability.Read); Add(configuration.Sandbox.Filesystem.Write, FileCapability.Write); Add(configuration.Sandbox.Filesystem.Execute, FileCapability.Execute); Add(configuration.Sandbox.Filesystem.Delete, FileCapability.Delete); var settings = new WindowsSandboxSettings( fileEntries, configuration.Sandbox.Process.Approvals.Select(approval => new Agent.Windows.Security.ExecutableApproval(approval.Path, approval.Sha256)).ToArray(), configuration.Sandbox.Process.AllowShell, new SandboxJobLimits((uint)configuration.Sandbox.Process.MaxProcesses, checked((ulong)configuration.Sandbox.Resources.MemoryMb * 1024 * 1024)), configuration.Sandbox.Network.Mode, configuration.Sandbox.Network.Allow.Select(item => (item.Host, (IReadOnlyList)item.Ports)).ToArray(), journalDirectory, backupDirectory, checked((long)configuration.FileEdit.MemoryBackupLimitMb * 1024 * 1024), configuration.Limits.WholeReadBytes, configuration.Limits.SearchMaxResults, configuration.Limits.TailMaxLines, configuration.Sandbox.Resources.ProcessTimeoutSeconds); var backend = new WindowsToolBackend(settings, audit, new TaskGraphToolHandler(configuration.Planner.MaxTasks, sessionStore)); var allowed = InternalToolCatalog.All.Select(static item => item.Name).ToHashSet(StringComparer.Ordinal); return new InternalToolDispatcher(new DenyByDefaultPolicy(allowed), backend); void Add(IEnumerable paths, FileCapability capability) { foreach (string path in paths) fileEntries.Add(new FilePolicyEntry(Path.IsPathFullyQualified(path) ? path : ConfigurationLoader.Resolve(baseDirectory, path), capability)); } } private static AgentRuntimeSettings CreateRuntimeSettings(AgentConfiguration configuration, string userPrompt, string language, IReadOnlyList skills, LocaleCatalog locale) => new( userPrompt, language, skills, "Windows AppContainer, ACL, Job Object, child-process restriction, and WFP sandbox capabilities.", configuration.Agent.MaxIterations, configuration.Agent.SameFailureLimit, locale.Get("ollama.vision_unsupported"), configuration.Ollama.Options.NumCtx ?? 32768, configuration.Limits.ContextSummaryRatio, MaxTasks: configuration.Planner.MaxTasks, Model: configuration.Ollama.Model); }