using Agent.App.Cli; using Agent.App.Configuration; using Agent.App.Logging; using Agent.App.Sandbox; 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; using Agent.Admin.Client; 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); ConfigurationLoader.Validate(configuration, Path.GetDirectoryName(configPath)!); string 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); if (commandLine.Command is AppCommand.Apply or AppCommand.Show) return RunUnifiedManagementCommand(commandLine, configuration, configPath, logDirectory, locale); string stateDirectory = Path.Combine(baseDirectory, "state"); string journalDirectory = Path.Combine(stateDirectory, "journals"); string backupDirectory = Path.Combine(stateDirectory, "backups"); EffectiveSandboxPolicy effectiveSandboxPolicy = new AppliedSandboxConfiguration(configPath, configuration) .Compile(journalDirectory, backupDirectory); UserOnlyDirectory.Ensure(logDirectory); using var log = new RollingFileLog(logDirectory, configuration.Logging.MaxFileBytes, configuration.Logging.RetainedFiles, configuration.Logging.Level); log.Information(locale.Get("app.starting")); string helperPath = Path.Combine(AppContext.BaseDirectory, "admin", "agent-mcp-admin.exe"); WindowsPlatformCheckResult servePlatform = WindowsPlatformCheck.Run(helperPath); if (!servePlatform.Success) { string failure = locale.Format("app.preflight_failed", new Dictionary { ["operation"] = servePlatform.ErrorOperation ?? "privilege/BFE/WFP probe", ["error_code"] = servePlatform.ErrorCode, ["platform"] = servePlatform.PlatformSupported.ToString().ToLowerInvariant(), ["profile"] = servePlatform.ApplicationProfileAvailable.ToString().ToLowerInvariant(), ["bfe"] = servePlatform.BfeEngineOpen.ToString().ToLowerInvariant(), ["helper"] = servePlatform.HelperAvailable.ToString().ToLowerInvariant(), ["version"] = servePlatform.HelperVersionCompatible.ToString().ToLowerInvariant() }); log.Fatal(failure); return 2; } var sessionStore = new SessionStore(); InternalToolDispatcher toolDispatcher = await CreateToolDispatcherAsync(configuration, effectiveSandboxPolicy, logDirectory, sessionStore, new ElevatedNetworkSessionFactory(helperPath), CancellationToken.None).ConfigureAwait(false); 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: {FormatStartupError(args, ex)}"); 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; } } internal static string FormatStartupError(string[] args, Exception error) { string language = "en"; try { string directory = Path.Combine(AppContext.BaseDirectory, "config", "locales"); LocaleCatalog locale = LocaleCatalog.Load(directory, language, "en"); string key = error switch { CommandLineException => "cli.invalid", ConfigurationException => "config.invalid", LocaleException => "locale.invalid", _ => "app.io_error" }; string message = error is ConfigurationException configuration ? FormatConfigurationError(locale, configuration) : error.Message; return locale.Format(key, new Dictionary { ["message"] = message }); } catch { return error.Message; } } private static string FormatConfigurationError(LocaleCatalog locale, ConfigurationException error) { string? key = error.Code switch { "sandbox.configuration_not_applied" => "sandbox.error.configuration_not_applied", _ => null }; string localized = key is null ? error.Message : locale.Format(key, new Dictionary { ["target"] = error.Target ?? "-" }); string resolution = key is null || error.Resolution is null ? error.Resolution ?? "-" : locale.Get(key + ".resolution"); return $"code={error.Code}; category={error.Category}; target={error.Target ?? "-"}; message={localized}; resolution={resolution}"; } private static int RunUnifiedManagementCommand(CommandLine command, AgentConfiguration configuration, string configPath, string logDirectory, LocaleCatalog locale) { var appliedConfiguration = new AppliedSandboxConfiguration(configPath, configuration); AppliedConfigurationPreview preview = appliedConfiguration.Resolve(); AppliedConfigurationDocument? applied; try { applied = appliedConfiguration.LoadApplied(); } catch (ConfigurationException error) when (command.Command == AppCommand.Apply) { applied = null; Console.Out.WriteLine(locale.Format("apply.store_unreadable", new Dictionary { ["message"] = error.Message })); } bool current = appliedConfiguration.IsApplied(preview, applied); PrintAppliedState(preview, applied, current, locale); if (command.Command == AppCommand.Show) return 0; Console.Out.WriteLine("\n" + locale.Get(current ? "apply.no_changes" : "apply.replace_snapshot")); ConfirmMutation(command.Yes, locale.Get("apply.prompt")); // Application Profile creation is a normal-user, non-elevated configuration operation. // Server startup remains read-only and only derives the fixed SID. _ = AppContainerProfile.EnsureFixed(); UserOnlyDirectory.Ensure(logDirectory); AppliedConfigurationDocument document = appliedConfiguration.Apply(); var audit = new JsonLinesSecurityAuditSink(Path.Combine(logDirectory, "audit.jsonl")); audit.Write(new(DateTimeOffset.UtcNow, Guid.Empty, "sandbox.configuration.apply", "applied", new Dictionary { ["config_fingerprint"] = document.ConfigFingerprint, ["execution_entries"] = document.Execution.Count.ToString(System.Globalization.CultureInfo.InvariantCulture), ["non_interactive"] = command.Yes.ToString() })); Console.Out.WriteLine("\n" + locale.Get("apply.success")); return 0; } private static void PrintAppliedState(AppliedConfigurationPreview preview, AppliedConfigurationDocument? applied, bool current, LocaleCatalog locale) { Console.Out.WriteLine(locale.Format("show.configuration", new Dictionary { ["path"] = preview.ConfigPath })); Console.Out.WriteLine(locale.Format("show.applied", new Dictionary { ["value"] = locale.Get(current ? "show.yes" : "show.no") })); Console.Out.WriteLine(locale.Format("show.fingerprint", new Dictionary { ["value"] = locale.Get(applied is null ? "show.not_applied" : current ? "show.matched" : "show.changed") })); Console.Out.WriteLine(locale.Get("show.platform")); Console.Out.WriteLine("\n" + locale.Get("show.filesystem")); Console.Out.WriteLine(locale.Get("show.filesystem_columns")); foreach (ResolutionItem item in preview.Filesystem) Console.Out.WriteLine($"{item.Kind}\t{item.LogicalPath}\t{item.ResolvedPath}\t{item.Access}\t{locale.Get("show.status." + FilesystemStatus(item, applied))}"); foreach (ResolutionItem item in applied?.Filesystem.Except(preview.Filesystem) ?? []) Console.Out.WriteLine($"{item.Kind}\t{item.LogicalPath}\t{item.ResolvedPath}\t{item.Access}\t{locale.Get("show.status.removed")}"); Console.Out.WriteLine("\n" + locale.Get("show.execution")); Console.Out.WriteLine(locale.Get("show.execution_columns")); foreach (AppliedExecutionEntry item in preview.Execution) Console.Out.WriteLine($"{item.Alias}\t{item.Classification}\t{item.ResolvedPath}\t{item.ResolvedRuntimeRoot ?? "-"}\t{item.Sha256}\t{locale.Get("show.status." + ExecutionStatus(item, applied))}"); foreach (AppliedExecutionEntry item in applied?.Execution.Where(old => preview.Execution.All(now => !now.Alias.Equals(old.Alias, StringComparison.OrdinalIgnoreCase))) ?? []) Console.Out.WriteLine($"{item.Alias}\t{item.Classification}\t{item.ResolvedPath}\t{item.ResolvedRuntimeRoot ?? "-"}\t{item.Sha256}\t{locale.Get("show.status.removed")}"); Console.Out.WriteLine("\n" + locale.Get("show.network")); string networkStatus = applied is not null && AppliedSandboxConfiguration.NetworkEquivalent(applied.Network, preview.Network) ? "active" : applied is null ? "added" : "changed"; Console.Out.WriteLine($"access={preview.Network.Access} endpoints={string.Join(',', preview.Network.Endpoints.Select(item => $"{item.Host}:{string.Join('/', item.Ports)}"))} status={locale.Get("show.status." + networkStatus)}"); Console.Out.WriteLine("\n" + locale.Get("show.resources")); string resourceStatus = applied?.Resources == preview.Resources ? "active" : applied is null ? "added" : "changed"; Console.Out.WriteLine($"profile={preview.Resources.Profile} memory_mb={preview.Resources.MemoryMb} process_limit={preview.Resources.ProcessLimit} timeout_seconds={preview.Resources.ExecutionTimeoutSeconds} status={locale.Get("show.status." + resourceStatus)}"); } private static string FilesystemStatus(ResolutionItem item, AppliedConfigurationDocument? applied) => applied is null ? "added" : applied.Filesystem.Contains(item) ? "active" : "changed"; private static string ExecutionStatus(AppliedExecutionEntry item, AppliedConfigurationDocument? applied) { if (applied is null) return "added"; AppliedExecutionEntry? previous = applied.Execution.FirstOrDefault(old => old.Alias.Equals(item.Alias, StringComparison.OrdinalIgnoreCase)); return previous is null ? "added" : previous == item ? "active" : "changed"; } private static async Task CreateToolDispatcherAsync(AgentConfiguration configuration, EffectiveSandboxPolicy effectiveSandboxPolicy, string logDirectory, SessionStore? sessionStore, IElevatedNetworkSessionFactory elevatedNetwork, CancellationToken cancellationToken) { Directory.CreateDirectory(effectiveSandboxPolicy.JournalDirectory); Directory.CreateDirectory(effectiveSandboxPolicy.BackupDirectory); var audit = new JsonLinesSecurityAuditSink(Path.Combine(logDirectory, "audit.jsonl")); RecoveryResult recovery = await new RecoveryManager(effectiveSandboxPolicy.JournalDirectory, audit, elevatedNetwork) .RecoverAllAsync(cancellationToken).ConfigureAwait(false); if (!recovery.IsClean) throw new InvalidOperationException("Recovery did not reach a clean state: " + string.Join(" | ", recovery.Errors)); var backend = new WindowsToolBackend(effectiveSandboxPolicy, audit, new TaskGraphToolHandler(configuration.Planner.MaxTasks, sessionStore), elevatedNetwork); var allowed = InternalToolCatalog.All.Select(static item => item.Name).ToHashSet(StringComparer.Ordinal); return new InternalToolDispatcher(new DenyByDefaultPolicy(allowed), backend); } internal static AgentRuntimeSettings CreateRuntimeSettings(AgentConfiguration configuration, string userPrompt, string language, IReadOnlyList skills, LocaleCatalog locale) => new( userPrompt, language, skills, "Windows LPAC, ACL, Job Object process-tree, 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); private static void ConfirmMutation(bool yes, string prompt) { if (yes) return; if (Console.IsInputRedirected) throw new CommandLineException("Interactive confirmation is unavailable; review the displayed diff and re-run with --yes."); Console.Out.Write($"{prompt} [y/N] "); if (!string.Equals(Console.ReadLine(), "y", StringComparison.OrdinalIgnoreCase)) throw new CommandLineException("Operation cancelled."); } }