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 System.Text.Json; 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); if (commandLine.Command is not (AppCommand.Serve or AppCommand.Check)) return RunManagementCommand(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 SandboxEnvironmentResolver(configPath, configuration) .Compile(journalDirectory, backupDirectory); 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 }); Console.Error.WriteLine(failure); return 2; } Console.Out.WriteLine(locale.Get("check.success")); return 0; } UserOnlyDirectory.Ensure(logDirectory); using var log = new RollingFileLog(logDirectory, configuration.Logging.MaxFileBytes, configuration.Logging.RetainedFiles, configuration.Logging.Level); 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, effectiveSandboxPolicy, 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: {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"; for (int i = 0; i + 1 < args.Length; i++) if (args[i] == "--lang") language = args[i + 1]; 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.resolution_unconfirmed" => "sandbox.error.resolution_unconfirmed", "sandbox.resolution_changed" => "sandbox.error.resolution_changed", "executable.approval_missing" => "sandbox.error.approval_missing", "executable.resolution_changed" => "sandbox.error.executable_resolution_changed", "executable.hash_changed" => "sandbox.error.executable_hash_changed", "executable.shell_unsupported" => "sandbox.error.shell_unsupported", _ => 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 InternalToolDispatcher CreateToolDispatcher(AgentConfiguration configuration, EffectiveSandboxPolicy effectiveSandboxPolicy, string logDirectory, SessionStore? sessionStore = null) { Directory.CreateDirectory(effectiveSandboxPolicy.JournalDirectory); Directory.CreateDirectory(effectiveSandboxPolicy.BackupDirectory); var audit = new JsonLinesSecurityAuditSink(Path.Combine(logDirectory, "audit.jsonl")); RecoveryResult recovery = new RecoveryManager(effectiveSandboxPolicy.JournalDirectory, audit).RecoverAll(); 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)); 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 int RunManagementCommand(CommandLine command, AgentConfiguration configuration, string configPath, string logDirectory, LocaleCatalog locale) { var resolver = new SandboxEnvironmentResolver(configPath, configuration); var approvals = new ExecutableApprovalStore(); var audit = new JsonLinesSecurityAuditSink(Path.Combine(logDirectory, "audit.jsonl")); switch (command.Command) { case AppCommand.ExecutableResolve: { IReadOnlyList candidates = ExecutableResolver.ResolveCandidates(command.Alias!, ResolveCliPath(command.ExecutablePath, configPath)); foreach (string candidate in candidates) Console.Out.WriteLine(candidate); if (candidates.Count > 1) Console.Out.WriteLine(locale.Get("management.ambiguous_candidates")); return 0; } case AppCommand.ExecutableApprove: { UserOnlyDirectory.Ensure(logDirectory); EnsureConfiguredAlias(configuration, command.Alias!, out bool shell); if (command.RuntimeRoot is null && !command.NoRuntime) throw new CommandLineException("Specify either --runtime-root or --no-runtime when approving an Executable Alias."); IReadOnlyList candidates = ExecutableResolver.ResolveCandidates(command.Alias!, ResolveCliPath(command.ExecutablePath, configPath)); if (candidates.Count != 1) throw new ConfigurationException($"Executable Alias '{command.Alias}' is ambiguous. Re-run with --path and one of:{Environment.NewLine}{string.Join(Environment.NewLine, candidates)}"); string resolved = candidates[0]; if (shell && !ExecutableResolver.IsSupportedShell(resolved)) throw new ConfigurationException($"Shell Alias '{command.Alias}' must resolve to cmd.exe, powershell.exe, or pwsh.exe.", code: "executable.shell_unsupported", category: "sandbox", target: resolved, resolution: "Select a supported Shell executable and approve it explicitly."); string? runtimeRoot = command.RuntimeRoot is null ? null : ResolvedPath.Resolve(ResolveCliPath(command.RuntimeRoot, configPath)!); if (runtimeRoot is not null && !Directory.Exists(runtimeRoot)) throw new ConfigurationException($"Runtime Root is not a directory: '{runtimeRoot}'."); string sha256 = EntryExecutablePolicy.ComputeSha256(resolved); Console.Out.WriteLine(locale.Format("management.approval_diff", new Dictionary { ["alias"] = command.Alias, ["classification"] = shell ? "shell" : "executable", ["path"] = resolved, ["sha256"] = sha256, ["runtime_root"] = runtimeRoot ?? "(none)" })); ConfirmMutation(command.Yes, locale.Get("management.approve_prompt")); approvals.Approve(new(command.Alias!, shell ? "shell" : "executable", command.ExecutablePath ?? resolved, resolved, sha256, runtimeRoot, DateTimeOffset.UtcNow)); audit.Write(new(DateTimeOffset.UtcNow, Guid.Empty, "executable.approval.add", "approved", new Dictionary { ["alias"] = command.Alias!, ["resolved_path"] = resolved, ["non_interactive"] = command.Yes.ToString() })); return 0; } case AppCommand.ExecutableList: foreach (ExecutableApprovalRecord item in approvals.List()) Console.Out.WriteLine($"{item.Alias}\t{item.Classification}\t{item.ResolvedPath}\t{item.Sha256}\t{item.RuntimeRoot ?? "-"}"); return 0; case AppCommand.ExecutableRevoke: { UserOnlyDirectory.Ensure(logDirectory); ExecutableApprovalRecord record = approvals.Find(command.Alias!) ?? throw new ConfigurationException($"Executable Alias '{command.Alias}' is not approved."); Console.Out.WriteLine(locale.Format("management.revoke_diff", new Dictionary { ["alias"] = record.Alias, ["path"] = record.ResolvedPath })); ConfirmMutation(command.Yes, locale.Get("management.revoke_prompt")); _ = approvals.Revoke(record.Alias); audit.Write(new(DateTimeOffset.UtcNow, Guid.Empty, "executable.approval.remove", "revoked", new Dictionary { ["alias"] = record.Alias, ["resolved_path"] = record.ResolvedPath, ["non_interactive"] = command.Yes.ToString() })); return 0; } case AppCommand.SandboxResolve: PrintResolution(resolver.Resolve(), "text", locale); return 0; case AppCommand.SandboxConfirm: { UserOnlyDirectory.Ensure(logDirectory); SandboxResolutionPreview preview = resolver.Resolve(); PrintResolution(preview, "text", locale); ConfirmMutation(command.Yes, locale.Get("management.sandbox_confirm_prompt")); SandboxResolutionDocument document = resolver.Confirm(); audit.Write(new(DateTimeOffset.UtcNow, Guid.Empty, "sandbox.resolution.confirm", "confirmed", new Dictionary { ["config_fingerprint"] = document.ConfigFingerprint, ["non_interactive"] = command.Yes.ToString() })); return 0; } case AppCommand.SandboxShow: { SandboxResolutionPreview preview = resolver.Resolve(); SandboxResolutionDocument? confirmed = new SandboxResolutionStore(configPath).Load(); bool isConfirmed = confirmed is not null && confirmed.ConfigFingerprint == preview.Fingerprint; EffectiveSandboxPolicy? policy = isConfirmed ? resolver.Compile(Path.Combine(Path.GetDirectoryName(configPath)!, "state", "journals"), Path.Combine(Path.GetDirectoryName(configPath)!, "state", "backups")) : null; object? effective = policy is null ? null : EffectivePolicyView(policy); if (command.Format == "json") Console.Out.WriteLine(JsonSerializer.Serialize(new { current = preview, confirmed, effective_policy = effective }, new JsonSerializerOptions { WriteIndented = true })); else { PrintResolution(preview, "text", locale); Console.Out.WriteLine(locale.Format("management.confirmed", new Dictionary { ["value"] = locale.Get(isConfirmed ? "management.yes" : "management.no") })); if (effective is not null) Console.Out.WriteLine(JsonSerializer.Serialize(effective, new JsonSerializerOptions { WriteIndented = true })); } return 0; } default: throw new CommandLineException("Unsupported management command."); } } private static void EnsureConfiguredAlias(AgentConfiguration configuration, string alias, out bool shell) { bool executable = configuration.Sandbox.Execution.Executables.Contains(alias, StringComparer.OrdinalIgnoreCase); shell = configuration.Sandbox.Execution.Shells.Contains(alias, StringComparer.OrdinalIgnoreCase); if (!executable && !shell) throw new ConfigurationException($"Executable Alias '{alias}' is not declared in sandbox.execution.executables or sandbox.execution.shells."); } 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."); } private static void PrintResolution(SandboxResolutionPreview preview, string format, LocaleCatalog locale) { if (format == "json") { Console.Out.WriteLine(JsonSerializer.Serialize(preview, new JsonSerializerOptions { WriteIndented = true })); return; } Console.Out.WriteLine(locale.Format("management.resolution_header", new Dictionary { ["config"] = preview.ConfigPath, ["fingerprint"] = preview.Fingerprint })); foreach (ResolutionItem item in preview.Items) Console.Out.WriteLine($"{item.Kind}\t{item.Access}\t{item.LogicalPath} -> {item.ResolvedPath}"); } private static string? ResolveCliPath(string? path, string configPath) => path is null ? null : (Path.IsPathFullyQualified(path) ? path : Path.Combine(Path.GetDirectoryName(configPath)!, path)); private static object EffectivePolicyView(EffectiveSandboxPolicy policy) => new { filesystem_roots = policy.Files.Select(item => new { resolved_path = item.Path, capabilities = item.Capabilities.ToString(), origin = item.Origin }), approved_entry_executables = policy.EntryExecutables.Select(item => new { alias = item.Alias, resolved_path = item.ResolvedPath, sha256 = item.Sha256, classification = item.IsShell ? "approved_shell_executable" : "approved_entry_executable", runtime_root = item.RuntimeRoot }), runtime_roots = policy.RuntimeRoots, artifact_roots = policy.ArtifactRoots, network = new { access = policy.NetworkAccess, endpoints = policy.NetworkAllowances.Select(item => new { host = item.Host, ports = item.Ports }) }, resources = new { profile = policy.ResourceProfile, process_limit = policy.JobLimits.MaxProcesses, memory_bytes = policy.JobLimits.MaxJobMemoryBytes, execution_timeout_seconds = policy.ProcessTimeoutSeconds }, baseline_capabilities = policy.BaselineCapabilities ?? ["registryRead", "lpacInstrumentation"] }; }