335 lines
14 KiB
C#
335 lines
14 KiB
C#
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<ClawdDotNet.Core.Logging.LogLevel>(
|
||
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();
|
||
}
|
||
}
|