Geplante Agenten begannen bei jedem Cron-Lauf bei null. Ein Agent, der alle 30 Minuten lief, wusste nichts von seinem letzten Durchgang — er rief dieselben Quellen ab, zog dieselben Schluesse und konnte keine Entwicklung ueber Zeit verfolgen. Das war zugleich die groesste Faehigkeitsluecke und eine dauerhafte Token-Verschwendung. Speicher-Fundament SqliteStorage buendelt den Zugang zur Instanz-Datenbank und aktiviert WAL, busy_timeout und Connection-Pooling. Vorher oeffnete jeder Aufruf eine Verbindung ohne diese Einstellungen; bei mehreren gleichzeitig schreibenden Agenten gab das "database is locked". Das sah nach einer Grenze von SQLite aus, war aber nur fehlende Konfiguration. Zwei Tests decken das gezielt ab. Gedaechtnis Typisierte Tabelle statt JSON in einer Wert-Spalte — nur so laesst sich filtern, sortieren und spaeter auswerten. Das Schema ist schlicht gehalten, damit eine MySQL-Variante spaeter dieselbe Struktur mit wenigen Dialektunterschieden bekommen kann. Der wichtigste Teil ist der optionale Schluessel: Erneutes Merken darunter aktualisiert den Eintrag, statt einen zweiten anzulegen. Ohne das wuechse das Gedaechtnis eines halbstuendlich laufenden Agenten um 48 Eintraege pro Tag zur selben Sache. Beobachtungen ohne Schluessel sammeln sich weiterhin an, wenn ein Verlauf entstehen soll. Der Abruf sortiert nach Wichtigkeit, dann Aktualitaet — wesentlich, weil das Ergebnis begrenzt wird und bei einer Kappung das Wichtigste ueberleben muss. Zusaetzlich greift eine Zeichenobergrenze, damit ein Abruf den Kontext nicht sprengt. Die Trennung privat/geteilt ist absichtlich dieselbe wie beim FileRW-Tool, damit das Konzept fuer Agenten wiedererkennbar bleibt. Beim Testen fiel auf, dass das Maskieren der LIKE-Platzhalter falsch war: Die Zeichen wurden entfernt statt maskiert, wodurch eine Suche nach einem Prozentzeichen zu einem leeren Muster und damit zu einem Treffer auf alles wurde. Jetzt mit ESCAPE-Klausel. 338 Tests gruen (190 Core, 148 Tools). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
254 lines
9.6 KiB
C#
254 lines
9.6 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.Scheduling;
|
||
using ClawdDotNet.Core.Tools;
|
||
using ClawdDotNet.Core.State;
|
||
using ClawdDotNet.Core.Storage;
|
||
using ClawdDotNet.Core.Memory;
|
||
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);
|
||
|
||
// ─── 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());
|
||
|
||
// ─── 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;
|
||
AgentScheduler? agentScheduler = null;
|
||
ToolJobScheduler? toolJobScheduler = 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);
|
||
|
||
agentEngine = new AgentEngine(
|
||
openRouterClient, toolRegistry, permissionGate, stateStore, loggerFactory, memoryRepository);
|
||
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();
|
||
agentScheduler = new AgentScheduler(agentEngine, instanceConfig.InstanceId, loggerFactory);
|
||
agentScheduler.RegisterAll(instanceConfig.Agents);
|
||
|
||
toolJobScheduler = new ToolJobScheduler(agentEngine, toolRegistry, stateStore, loggerFactory, instanceConfig.InstanceId);
|
||
toolJobScheduler.RegisterAll(instanceConfig.Agents);
|
||
|
||
coreLogger.LogInformation("AgentEngine und Scheduler 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");
|
||
}
|
||
}
|
||
|
||
// ─── 7. MainForm starten ───
|
||
var form = new frm_main(
|
||
settingsManager,
|
||
instanceConfig,
|
||
instancePath,
|
||
logDir,
|
||
loggerFactory,
|
||
toolRegistry,
|
||
dirManager,
|
||
agentEngine,
|
||
agentScheduler,
|
||
toolJobScheduler);
|
||
|
||
Application.Run(form);
|
||
|
||
// ─── 8. Aufräumen ───
|
||
coreLogger.LogInformation("ClawdDotNet wird beendet");
|
||
|
||
if (toolJobScheduler is not null)
|
||
toolJobScheduler.DisposeAsync().AsTask().GetAwaiter().GetResult();
|
||
|
||
if (agentScheduler is not null)
|
||
agentScheduler.DisposeAsync().AsTask().GetAwaiter().GetResult();
|
||
|
||
if (tgClientManager is not null)
|
||
tgClientManager.DisposeAsync().AsTask().GetAwaiter().GetResult();
|
||
|
||
openRouterClient?.Dispose();
|
||
loggerFactory.Dispose();
|
||
}
|
||
}
|