using ClawdDotNet.Core.Api; using ClawdDotNet.Core.Config; using ClawdDotNet.Core.Engine; using ClawdDotNet.Core.Logging; using ClawdDotNet.Core.Security; using ClawdDotNet.Core.Tools; using ClawdDotNet.Core.State; using ClawdDotNet.Core.Storage; using ClawdDotNet.Core.Memory; using ClawdDotNet.Core.Accounting; using ClawdDotNet.Services; using ClawdDotNet.Tools.FileRW; using ClawdDotNet.Tools.Telegram; using ClawdDotNet.Tools.Mail; using ClawdDotNet.Tools.Database; using ClawdDotNet.Tools.FTP; using ClawdDotNet.Tools.DirectAPI; using ClawdDotNet.Tools.WebFetch; using ClawdDotNet.Tools.WebMonitor; using ClawdDotNet.Tools.AgentComm; using ClawdDotNet.Tools.AgentSpawn; using ClawdDotNet.Tools.SocialMediaManager; using ClawdDotNet.Tools.AgentEditor; using ClawdDotNet.Tools.TelegramClient; using ClawdDotNet.UI; using Microsoft.Extensions.Logging; namespace ClawdDotNet; internal static class Program { [STAThread] static void Main(string[] args) { ApplicationConfiguration.Initialize(); // ─── 0. Embedded UI extrahieren ─── EmbeddedUiManager.ExtractToTemp(); // ─── 1. App-Settings laden ─── var settingsManager = new SettingsManager(); settingsManager.Load(); var appSettings = settingsManager.AppSettings; // ─── 2. InstanceDirectoryManager erstellen ─── var instancesDir = Path.GetFullPath(appSettings.InstancesDirectory); var dirManager = new InstanceDirectoryManager(instancesDir); // ─── 3. Instanz-Verzeichnis auswählen ─── string instancePath; #if DEBUG // Im Debug-Modus: Dev-Instanz automatisch erstellen/starten const string devInstanceName = "Dev"; instancePath = dirManager.GetInstancePath(devInstanceName); if (!Directory.Exists(instancePath)) { dirManager.CreateInstance(devInstanceName); } #else // Im Release-Modus: InstanceManager anzeigen var instanceManager = new frm_InstanceManager(dirManager, settingsManager); var dialogResult = instanceManager.ShowDialog(); if (dialogResult != DialogResult.OK || string.IsNullOrWhiteSpace(instanceManager.SelectedInstancePath)) { return; // Benutzer hat abgebrochen } instancePath = instanceManager.SelectedInstancePath; instanceManager.Dispose(); #endif // ─── 4. Instanz-Config aus Verzeichnisstruktur laden ─── InstanceConfig instanceConfig; try { instanceConfig = dirManager.LoadInstanceConfig(instancePath); } catch (Exception ex) { MessageBox.Show( $"Fehler beim Laden der Instanz:\n{instancePath}\n\n{ex.Message}", "ClawdDotNet – Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } // ─── 5. Logging-System initialisieren ─── var logDir = Path.GetFullPath( !string.IsNullOrWhiteSpace(instanceConfig.LogDirectory) ? instanceConfig.LogDirectory : appSettings.LogDirectory); var minLevel = Enum.TryParse( appSettings.MinimumLogLevel, true, out var ml) ? ml : ClawdDotNet.Core.Logging.LogLevel.Info; var loggerFactory = LoggingExtensions.CreateClawdLoggerFactory(logDir, minLevel); var coreLogger = loggerFactory.CreateLogger("ClawdDotNet.Core.Startup"); coreLogger.LogInformation("ClawdDotNet startet – Instanz: {Instance} ({Id})", instanceConfig.InstanceName, instanceConfig.InstanceId); coreLogger.LogInformation("Instanz-Verzeichnis: {Path}", instancePath); // ─── 5b. Lizenzprüfung ─── // Entfällt in dieser Fassung. Startprüfung, laufende Nachprüfung und die // Deploymentcenter-Anbindung (Watchdog, Updates, Fehler-Stream, Bugtracker) // liegen in ClawdDotNet.App (AppHost); Einstiegspunkt ist // src/ClawdDotNet.Desktop. Diese Datei ist nur noch Vorlage — siehe // ClawdDotNet.slnx. // ─── 6. Core-Komponenten erzeugen ─── var toolRegistry = new ToolRegistry(); toolRegistry.Register(new FileRWTool()); toolRegistry.Register(new TelegramTool()); toolRegistry.Register(new MailTool()); toolRegistry.Register(new DatabaseTool()); toolRegistry.Register(new FTPTool()); toolRegistry.Register(new DirectApiTool()); toolRegistry.Register(new WebFetchTool()); toolRegistry.Register(new WebMonitorTool()); toolRegistry.Register(new AgentCommTool()); toolRegistry.Register(new SocialMediaManagerTool()); toolRegistry.Register(new AgentSpawnTool()); toolRegistry.Register(new AgentEditorTool()); toolRegistry.Register(new ClawdDotNet.Tools.Memory.MemoryTool()); toolRegistry.Register(new ClawdDotNet.Tools.Taskboard.TaskboardTool()); // ─── 6a. TelegramClient (MTProto User-API) ─── TelegramClientManager? tgClientManager = null; if (instanceConfig.TelegramClient is not null) { tgClientManager = new TelegramClientManager( instanceConfig, instancePath, loggerFactory.CreateLogger("ClawdDotNet.Tools.TelegramClient")); toolRegistry.Register(new TelegramClientTool(tgClientManager)); coreLogger.LogInformation("TelegramClient-Tool registriert"); } var permissionGate = new PermissionGate(); // TODO: Tools aus tools/ Ordner laden // foreach (var toolDll in Directory.GetFiles("./tools", "*.dll")) // LoadToolPlugin(toolDll, toolRegistry); OpenRouterClient? openRouterClient = null; AgentEngine? agentEngine = null; ClawdDotNet.Core.Tasks.TaskScanner? taskScanner = null; ClawdDotNet.Core.Staging.StagingService? stagingService = null; SqliteUsageRepository? usageRepository = null; if (!string.IsNullOrWhiteSpace(instanceConfig.OpenRouterApiKey)) { openRouterClient = new OpenRouterClient( instanceConfig.OpenRouterApiKey, loggerFactory.CreateLogger("ClawdDotNet.Core.Api.OpenRouterClient")); // ModelTypeConverter mit dem Client verbinden für PropertyGrid-Dropdown ClawdDotNet.Models.ModelTypeConverter.Client = openRouterClient; // ─── 6.1. Speicher initialisieren ─── // Eine Datenbank je Instanz; StateStore und Gedächtnis teilen sie sich. var storage = new SqliteStorage(Path.Combine(instancePath, "state.db")); var stateStore = new SqliteStateStore(storage); var memoryRepository = new SqliteMemoryRepository(storage); var taskRepository = new ClawdDotNet.Core.Tasks.SqliteTaskRepository(storage); var auditRepository = new ClawdDotNet.Core.Audit.SqliteAuditRepository(storage); var stagingRepository = new ClawdDotNet.Core.Staging.SqliteStagingRepository(storage); var stagingGate = new ClawdDotNet.Core.Staging.StagingGate( new ClawdDotNet.Core.Staging.StagingPolicy(), stagingRepository); usageRepository = new SqliteUsageRepository(storage); // Preise fürs Budget: Ohne sie greift nur die Token-Grenze. var pricingCatalog = new ModelPricingCatalog(); _ = Task.Run(async () => { try { pricingCatalog.Load(await openRouterClient.GetAvailableModelsAsync()); } catch { /* Ohne Preise bleibt die Kostengrenze wirkungslos, die Token-Grenze nicht. */ } }); agentEngine = new AgentEngine( openRouterClient, toolRegistry, permissionGate, stateStore, loggerFactory, memoryRepository, usageRepository, pricingCatalog, taskRepository, auditRepository, stagingGate) { InstanceBudget = instanceConfig.Budget }; agentEngine.SetAgentConfigProvider( () => instanceConfig.Agents, instanceConfig.InstanceId, agentId => { var agent = instanceConfig.Agents.FirstOrDefault(a => a.AgentId == agentId); if (agent is null) return null; return string.IsNullOrWhiteSpace(agent.AgentDir) ? null : agent.AgentDir; }); agentEngine.LoadPersistedChats(); // ─── Taskboard: Dateien in die DB spiegeln (Reconciliation beim Start) ─── // Verwaiste Claims eines abgestürzten Laufs lösen, dann alle Task-Dateien // importieren. Nicht blockierend — der Start soll nicht auf das Dateisystem warten. var sharedWorkspace = instanceConfig.Agents .Select(a => a.SharedWorkspacePath) .FirstOrDefault(p => !string.IsNullOrWhiteSpace(p)); if (!string.IsNullOrWhiteSpace(sharedWorkspace)) { var board = new ClawdDotNet.Core.Tasks.TaskboardService( taskRepository, Path.Combine(sharedWorkspace, "tasks")); // Staging (A2): Freigabe-/Ablehnungs-Dienst für die Review-Oberfläche. // Weckt den Agenten nach der Entscheidung über einen Folge-Task (A1). stagingService = new ClawdDotNet.Core.Staging.StagingService( stagingRepository, agentEngine, board, loggerFactory, auditRepository); // Scanner (A1): der Taktgeber. Startet erst, wenn die Reconciliation durch // ist — sonst könnte er einen noch nicht bereinigten Claim antreffen. // Der Scanner ist der EINZIGE periodische Treiber (A1) — er ersetzt die // früheren AgentScheduler/ToolJobScheduler. Geplante Agent-Läufe wie auch // Tool-Job-Polls sind jetzt Tasks. var dispatcher = new ClawdDotNet.Core.Tasks.EngineTaskDispatcher( agentEngine, () => instanceConfig.Agents, instanceConfig.InstanceId, toolRegistry, stateStore, loggerFactory); taskScanner = new ClawdDotNet.Core.Tasks.TaskScanner( taskRepository, dispatcher, loggerFactory); _ = Task.Run(async () => { try { var reset = await taskRepository.ReleaseStaleClaimsAsync( DateTime.UtcNow.AddMinutes(-15), DateTime.UtcNow, CancellationToken.None); // Einmalige Übernahme der alten coordination/task_*-Dateien … var coordMigration = new ClawdDotNet.Core.Tasks.CoordinationMigration( board, Path.Combine(sharedWorkspace, "coordination"), loggerFactory); var migratedCoord = await coordMigration.RunAsync(CancellationToken.None); // … und der alten scheduler/toolJobs-Konfiguration in Tasks. var schedMigration = new ClawdDotNet.Core.Tasks.SchedulerTaskMigration( board, taskRepository, loggerFactory); var migratedSched = await schedMigration.RunAsync(instanceConfig.Agents, CancellationToken.None); var imported = await board.ImportAllAsync(CancellationToken.None); coreLogger.LogInformation( "Taskboard bereit: {Imported} Aufgabe(n), migriert {Coord} coordination + {Sched} scheduler, {Reset} verwaiste Claims zurückgesetzt", imported, migratedCoord, migratedSched, reset); taskScanner.Start(); } catch (Exception ex) { coreLogger.LogWarning(ex, "Taskboard-Reconciliation beim Start fehlgeschlagen"); } }); } coreLogger.LogInformation("AgentEngine und Scanner erstellt, Chat-Verläufe geladen"); } else { coreLogger.LogWarning("Kein OpenRouter API-Key konfiguriert – Agenten sind deaktiviert"); } // ─── 6b. TelegramClient verbinden (Session oder interaktiver Login) ─── if (tgClientManager is not null) { tgClientManager.OnLoginCodeRequired += async (prompt) => { string? code = null; await Task.Run(() => { code = Microsoft.VisualBasic.Interaction.InputBox( prompt, "Telegram Verifizierung", ""); }); return code ?? ""; }; tgClientManager.On2FAPasswordRequired += async () => { string? pw = null; await Task.Run(() => { pw = Microsoft.VisualBasic.Interaction.InputBox( "Bitte 2FA-Passwort eingeben:", "Telegram 2FA", ""); }); return pw ?? ""; }; try { tgClientManager.ConnectAsync(CancellationToken.None) .GetAwaiter().GetResult(); } catch (Exception ex) { coreLogger.LogError(ex, "Telegram: Login fehlgeschlagen"); } } // ─── 6c. Instanz-Watchdog ─── // Entfällt hier ebenfalls. Der Heartbeat ans Deploymentcenter (ein Monitor je // Instanz, mit Gesundheitsprüfungen und angekündigtem Ende) steht in // ClawdDotNet.App/Services/DeploymentcenterService.cs. // ─── 7. MainForm starten ─── var form = new frm_main( settingsManager, instanceConfig, instancePath, logDir, loggerFactory, toolRegistry, dirManager, agentEngine, taskScanner, stagingService); Application.Run(form); // ─── 8. Aufräumen ─── coreLogger.LogInformation("ClawdDotNet wird beendet"); if (taskScanner is not null) taskScanner.DisposeAsync().AsTask().GetAwaiter().GetResult(); if (tgClientManager is not null) tgClientManager.DisposeAsync().AsTask().GetAwaiter().GetResult(); openRouterClient?.Dispose(); loggerFactory.Dispose(); } }