diff options
Diffstat (limited to 'src/Agent.App/Program.cs')
| -rw-r--r-- | src/Agent.App/Program.cs | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/src/Agent.App/Program.cs b/src/Agent.App/Program.cs new file mode 100644 index 0000000..3fae93f --- /dev/null +++ b/src/Agent.App/Program.cs @@ -0,0 +1,130 @@ +using Agent.App.Cli; +using Agent.App.Configuration; +using Agent.App.Logging; +using Agent.Core.Localization; +using Agent.Core.Prompts; +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<int> 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); + 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<string, object?> { ["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")); + InternalToolDispatcher toolDispatcher = CreateToolDispatcher(configuration, baseDirectory, logDirectory); + var runtimeObserver = new JsonLinesRuntimeObserver(logDirectory); + if (configuration.Mcp.Transport == "stdio") + { + string userPrompt = File.ReadAllText(ConfigurationLoader.Resolve(baseDirectory, configuration.Agent.UserSystemPrompt)); + string skillDirectory = ConfigurationLoader.Resolve(baseDirectory, configuration.Skills.Directory); + string[] skills = Directory.EnumerateFiles(skillDirectory, "*.md", SearchOption.TopDirectoryOnly).Select(File.ReadAllText).ToArray(); + var runtimeSettings = new AgentRuntimeSettings(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")); + 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).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); + string[] skills = Directory.EnumerateFiles(skillDirectory, "*.md", SearchOption.TopDirectoryOnly).Select(File.ReadAllText).ToArray(); + var runtimeSettings = new AgentRuntimeSettings(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")); + 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).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) + { + 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<FilePolicyEntry>(); + 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<int>)item.Ports)).ToArray(), + journalDirectory, backupDirectory, checked((long)configuration.FileEdit.MemoryBackupLimitMb * 1024 * 1024), + configuration.Limits.WholeReadBytes, configuration.Limits.SearchMaxResults, configuration.Limits.TailMaxLines); + var backend = new WindowsToolBackend(settings, audit, new TaskGraphToolHandler(configuration.Planner.MaxTasks)); + var allowed = InternalToolCatalog.All.Select(static item => item.Name).ToHashSet(StringComparer.Ordinal); + return new InternalToolDispatcher(new DenyByDefaultPolicy(allowed), backend); + + void Add(IEnumerable<string> paths, FileCapability capability) + { + foreach (string path in paths) fileEntries.Add(new FilePolicyEntry(Path.IsPathFullyQualified(path) ? path : ConfigurationLoader.Resolve(baseDirectory, path), capability)); + } + } +} |
