# ClawdDotNet – Tool-Entwicklungsanleitung

## Übersicht

Tools sind eigenständige Plugins, die Agenten Zugriff auf externe Systeme geben (Datenbanken, Dateisysteme, E-Mail, APIs etc.). Jedes Tool ist ein separates .NET-Projekt, das ausschließlich `ClawdDotNet.Core` referenziert.

**Kernregeln:**
- Tools sind **niemals voneinander abhängig** (Tool A darf Tool B nicht kennen)
- Der Core kompiliert und läuft **ohne jedes Tool**
- Jedes Tool ist **pro Agent konfiguriert** (via `AgentConfig.Tools`)
- Tools werden zur Laufzeit über die `ToolRegistry` registriert

---

## Projekt-Struktur

```
ClawdDotNet.sln
├── src/
│   ├── ClawdDotNet.Core/                ← Klassenbibliothek (keine Tool-Verweise!)
│   ├── ClawdDotNet.Tools.MeinTool/      ← Dein Tool-Plugin
│   │   ├── ClawdDotNet.Tools.MeinTool.csproj
│   │   └── MeinToolTool.cs
│   ├── ClawdDotNet.App/                 ← Fachschicht ohne Oberflaeche (verweist auf Core + alle Tools)
│   └── ClawdDotNet.Desktop/             ← Oberflaeche in Avalonia (verweist auf App)
```

### Neues Tool-Projekt anlegen

```xml
<!-- ClawdDotNet.Tools.MeinTool.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <RootNamespace>ClawdDotNet.Tools.MeinTool</RootNamespace>
  </PropertyGroup>

  <ItemGroup>
    <!-- NUR Core referenzieren, KEINE anderen Tools -->
    <ProjectReference Include="..\ClawdDotNet.Core\ClawdDotNet.Core.csproj" />
  </ItemGroup>

  <ItemGroup>
    <!-- Tool-spezifische NuGet-Packages hier -->
  </ItemGroup>
</Project>
```

---

## IAgentTool implementieren

Jedes Tool implementiert das Interface `ClawdDotNet.Core.Tools.IAgentTool`:

```csharp
using System.Text.Json;
using ClawdDotNet.Core.Tools;

namespace ClawdDotNet.Tools.MeinTool;

public sealed class MeinToolTool : IAgentTool
{
    // 1. Eindeutiger Name – wird vom LLM in tool_calls verwendet
    public string Name => "MeinTool";

    // 2. Beschreibung für das LLM (geht in den System-Prompt)
    public string Description => "Beschreibung, was dieses Tool kann...";

    // 3. JSON Schema des Input-Objekts (OpenAI Function Calling Format)
    public JsonElement InputSchema { get; } = JsonDocument.Parse("""
        {
            "type": "object",
            "properties": {
                "action": {
                    "type": "string",
                    "enum": ["read", "write"],
                    "description": "Die auszuführende Aktion"
                },
                "data": {
                    "type": "string",
                    "description": "Eingabedaten"
                }
            },
            "required": ["action"]
        }
        """).RootElement.Clone();

    // 4. Ausführung
    public async Task<ToolResult> ExecuteAsync(
        JsonElement input,
        AgentToolContext context,
        CancellationToken ct)
    {
        // Action aus dem Input lesen
        var action = input.GetProperty("action").GetString()
            ?? throw new ArgumentException("'action' is required");

        // Konfiguration aus dem AgentToolContext lesen
        // NIEMALS hardcodierte Werte, immer aus context.ToolConfig!
        var meineSetting = context.ToolConfig.TryGetValue("meineSetting", out var val)
            ? val?.ToString() ?? ""
            : "";

        // Logger verwenden
        context.Logger.LogInformation("MeinTool executing action: {Action}", action);

        return action switch
        {
            "read" => await HandleReadAsync(input, context, ct),
            "write" => await HandleWriteAsync(input, context, ct),
            _ => ToolResult.Fail($"Unknown action: {action}")
        };
    }

    private async Task<ToolResult> HandleReadAsync(
        JsonElement input, AgentToolContext context, CancellationToken ct)
    {
        // Implementierung...
        await Task.CompletedTask; // Platzhalter
        return ToolResult.Ok("Ergebnis als JSON oder Text");
    }

    private async Task<ToolResult> HandleWriteAsync(
        JsonElement input, AgentToolContext context, CancellationToken ct)
    {
        // Implementierung...
        await Task.CompletedTask; // Platzhalter
        return ToolResult.Ok("Erfolgreich geschrieben");
    }
}
```

---

## Konfiguration pro Agent

Die Tool-Konfiguration ist **pro Agent** definiert, nicht global. Das bedeutet: Agent A kann auf Datenbank X zugreifen, Agent B auf Datenbank Y.

### In der Config-Datei (z.B. `config.json`):

```json
{
  "agents": [
    {
      "agentId": "analyst",
      "tools": {
        "MeinTool": {
          "meineSetting": "wert-fuer-agent-analyst",
          "andereOption": true
        }
      }
    },
    {
      "agentId": "developer",
      "tools": {
        "MeinTool": {
          "meineSetting": "wert-fuer-agent-developer",
          "andereOption": false
        }
      }
    }
  ]
}
```

### Konfiguration im Tool auslesen:

```csharp
// Im ExecuteAsync:
var setting = context.ToolConfig["meineSetting"]?.ToString();
var option = context.ToolConfig.TryGetValue("andereOption", out var v) 
    && v is JsonElement je 
    && je.GetBoolean();
```

**Wichtig:** `context.ToolConfig` enthält nur die Config für dieses Tool und diesen Agent. Du bekommst nie die Config eines anderen Agents oder eines anderen Tools.

---

## Tool im Host registrieren

Im `Host/Program.cs` (oder einer Startup-Klasse) wird das Tool registriert:

```csharp
var registry = new ToolRegistry();

// Jedes Tool einzeln registrieren
registry.Register(new MeinToolTool());
registry.Register(new DatabaseTool());
registry.Register(new FileRWTool());
// ...
```

Der `AgentEngine` nutzt dann die `ToolRegistry`, um für jeden Agent nur die erlaubten Tools bereitzustellen.

---

## Geplante Tools (Referenz)

### Database (`ClawdDotNet.Tools.Database`)

| Eigenschaft | Wert |
|---|---|
| Name | `Database` |
| NuGet | `MySqlConnector`, `MongoDB.Driver` |
| Aktionen | `query`, `insert`, `upsert` |

**Config-Felder:**
- `connectionString` – Datenbankverbindung (pro Agent!)
- `type` – `"mysql"` oder `"mongodb"`
- `allowedTables` – Liste erlaubter Tabellen (Whitelist)

**Sicherheit:**
- Parameterized Queries gegen SQL-Injection
- Nur Tabellen aus `allowedTables` erlaubt
- Connection String kommt IMMER aus `context.ToolConfig`

---

### FileRW (`ClawdDotNet.Tools.FileRW`)

| Eigenschaft | Wert |
|---|---|
| Name | `FileRW` |
| NuGet | (keine) |
| Aktionen | `read`, `write`, `append`, `list`, `delete` |

**Config-Felder:**
- `rootPath` – Basisverzeichnis (Agent ist darin eingesperrt)
- `allowWrite` – `true`/`false`
- `allowedExtensions` – z.B. `[".txt", ".json", ".html"]`

**Sicherheit (Pflicht):**
- Path-Traversal-Check: `Path.GetFullPath(requested).StartsWith(Path.GetFullPath(rootPath))`
- Nur erlaubte Dateiendungen
- Schreibzugriff nur wenn `allowWrite = true`

---

### Mail (`ClawdDotNet.Tools.Mail`)

| Eigenschaft | Wert |
|---|---|
| Name | `Mail` |
| NuGet | `MailKit` |
| Aktionen | `send`, `read_inbox`, `read_message`, `mark_read` |

**Config-Felder:**
- `imapHost`, `imapPort` – IMAP-Server
- `smtpHost`, `smtpPort` – SMTP-Server
- `username`, `password` – Zugangsdaten
- `allowedRecipients` – Whitelist erlaubter Empfänger

**Sicherheit:**
- Empfänger müssen in `allowedRecipients` stehen

---

## AgentToolContext – Referenz

```csharp
public sealed record AgentToolContext(
    string AgentId,          // ID des ausführenden Agents
    string InstanceId,       // ID der laufenden ClawdDotNet-Instanz
    IReadOnlyDictionary<string, object?> ToolConfig,  // Tool-Config für DIESEN Agent
    ILogger Logger,          // Logger (schreibt in Tool_{ToolName}.log)
    CancellationToken CancellationToken
);
```

---

## ToolResult – Referenz

```csharp
public sealed record ToolResult(
    bool Success,
    string Content,           // Geht zurück ans LLM
    string? ErrorMessage = null
);

// Hilfsmethoden:
ToolResult.Ok("Ergebnis als JSON oder Text");
ToolResult.Fail("Fehlerbeschreibung");
```

---

## Background Jobs (optional)

Manche Tools müssen regelmäßig im Hintergrund arbeiten, ohne dabei jedes Mal den Agenten (und damit das LLM) aufzuwecken. Beispiel: Ein Telegram-Tool prüft alle 30 Sekunden auf neue Nachrichten, weckt den Agenten aber **nur** wenn tatsächlich eine neue Nachricht vorliegt.

Dafür gibt es das optionale Interface `IToolJobProvider`. Ein Tool kann es **zusätzlich** zu `IAgentTool` implementieren — es ist kein Ersatz.

### IToolJobProvider implementieren

```csharp
using ClawdDotNet.Core.Tools;
using ClawdDotNet.Core.State;
using Microsoft.Extensions.Logging;

public sealed class MeinToolTool : IAgentTool, IToolJobProvider
{
    // ... IAgentTool-Implementierung wie oben ...

    // 1. Verfügbare Job-Typen deklarieren
    public IReadOnlyList<ToolJobDefinition> GetJobDefinitions() =>
    [
        new("meintool_check", "MeinTool Check", "Prüft regelmäßig auf Änderungen")
    ];

    // 2. Job-Tick ausführen (kein LLM, nur Tool-Code!)
    public async Task<ToolJobResult> ExecuteJobAsync(
        string jobTypeId,
        IReadOnlyDictionary<string, object?> toolConfig,
        IStateStore stateStore,
        ILogger logger,
        CancellationToken ct)
    {
        if (jobTypeId != "meintool_check")
            return ToolJobResult.NoAction($"Unbekannter Job: {jobTypeId}");

        // Config auslesen (gleiche Felder wie in ExecuteAsync)
        var apiKey = toolConfig.TryGetValue("apiKey", out var v)
            ? v?.ToString() ?? "" : "";

        // Zustand über IStateStore persistieren (überlebt Neustarts)
        var lastOffset = await stateStore.GetAsync("meintool_last_offset") ?? "0";

        // Prüfung durchführen...
        var newItems = await CheckForUpdatesAsync(apiKey, lastOffset, ct);

        if (newItems.Count == 0)
            return ToolJobResult.NoAction("Keine neuen Einträge");

        // Offset speichern
        await stateStore.SetAsync("meintool_last_offset", newItems.Last().Id);

        // Agent aufwecken mit Zusammenfassung
        return ToolJobResult.Wake(
            $"{newItems.Count} neue Einträge gefunden:\n" +
            string.Join("\n", newItems.Select(i => $"- {i.Title}")),
            logSummary: $"{newItems.Count} neue Einträge"
        );
    }
}
```

### ToolJobResult

Das Tool gibt deklarativ zurück, ob der Agent geweckt werden soll:

| Methode | Effekt |
|---------|--------|
| `ToolJobResult.NoAction(logSummary?)` | Nichts tun, optional Log-Eintrag |
| `ToolJobResult.Wake(wakeMessage, logSummary?)` | Agent **im bestehenden Chat** aufwecken (Default) |
| `ToolJobResult.WakeStateless(wakeMessage, logSummary?)` | Agent in **isolierter Session** aufwecken (kein Chatverlauf) |

**`Wake` vs `WakeStateless`:**
- `Wake` (Default) nutzt `ChatAsync` — die Nachricht erscheint im Chat-Tab, der Agent behält den gesamten Konversationsverlauf. Ideal für alles was Teil einer laufenden Interaktion ist (Telegram-Nachrichten, fertige Transkripte, E-Mail-Antworten).
- `WakeStateless` nutzt `RunAsync` — komplett isolierte Ausführung ohne Kontext. Nur für einmalige, kontextfreie Aufgaben verwenden.

**Wichtig:** Das Tool hat keinen direkten Zugriff auf den `AgentEngine`. Der `ToolJobScheduler` wertet das Ergebnis aus und weckt den Agenten bei Bedarf.

### IStateStore für Zustandstracking

`IStateStore` ist ein einfacher Key-Value-Store (SQLite-basiert), der zwischen Job-Ticks und Neustarts persistiert:

```csharp
// Wert lesen (null wenn nicht vorhanden)
string? value = await stateStore.GetAsync("mein_key");

// Wert schreiben
await stateStore.SetAsync("mein_key", "neuer_wert");
```

Verwende aussagekräftige Keys, z.B. `meintool_poll_offset` oder `meintool_last_check`.

### JSON-Konfiguration

Tool Jobs werden pro Agent in der `config.json` konfiguriert, parallel zum bestehenden `scheduler`:

```json
{
  "agentId": "support-bot",
  "tools": {
    "MeinTool": { "apiKey": "..." }
  },
  "scheduler": [
    { "cron": "0 8 * * 1-5", "taskMessage": "Morgenbericht erstellen" }
  ],
  "toolJobs": [
    {
      "jobId": "abc12345",
      "toolName": "MeinTool",
      "jobTypeId": "meintool_check",
      "cron": "*/5 * * * *",
      "enabled": true,
      "runOnStart": false
    }
  ]
}
```

| Feld | Beschreibung |
|------|-------------|
| `jobId` | Eindeutige ID (wird automatisch generiert) |
| `toolName` | Name des Tools (muss `IToolJobProvider` implementieren) |
| `jobTypeId` | Einer der von `GetJobDefinitions()` deklarierten IDs |
| `cron` | Cron-Ausdruck für die Ausführungsintervalle |
| `enabled` | Job aktiv/inaktiv |
| `runOnStart` | Beim Programmstart sofort einmal ausführen |

### Unterschied: Agent Wakeup vs Tool Job

| | Agent Wakeup (`scheduler`) | Tool Job (`toolJobs`) |
|---|---|---|
| **Ausführung** | Voller LLM-Run | Nur Tool-Code (kein LLM) |
| **Kosten** | Token pro Tick | Kostenlos pro Tick |
| **Agent-Aufruf** | Immer | Nur bei `ShouldWakeAgent` |
| **Anwendungsfall** | Regelmäßige Aufgaben | Polling, Monitoring, Checks |

---

## Checkliste für neue Tools

- [ ] Eigenes Projekt `ClawdDotNet.Tools.{Name}` anlegen
- [ ] Nur `ClawdDotNet.Core` referenzieren
- [ ] `IAgentTool` implementieren
- [ ] `InputSchema` als gültiges JSON Schema definieren
- [ ] Alle Konfiguration aus `context.ToolConfig` lesen
- [ ] Alle `await`-Aufrufe mit `CancellationToken` versehen
- [ ] `context.Logger` für Logging verwenden (kein `Console.WriteLine`)
- [ ] Sicherheitsprüfungen implementieren (je nach Tool-Typ)
- [ ] Im Host registrieren: `registry.Register(new MeinToolTool());`
- [ ] In der Solution-Datei (.slnx) einbinden
- [ ] NuGet-Packages in `NuGet.Config` PackageSourceMapping ergänzen
- [ ] *Optional:* `IToolJobProvider` implementieren (wenn Hintergrund-Polling nötig)
- [ ] *Optional:* `GetJobDefinitions()` mit eindeutigen `JobTypeId`s definieren
- [ ] *Optional:* `ExecuteJobAsync()` implementieren, `IStateStore` für Zustandstracking nutzen

---

## Logging

Das Logging-System trennt automatisch nach Modul. Wenn dein Tool den Logger aus dem `AgentToolContext` verwendet, landen die Logs automatisch in:

```
Logs/
├── 2026-05-12/
│   ├── Core.log            ← Engine, Scheduler, Config
│   ├── Host.log            ← Oberflaeche
│   ├── Tool_Database.log   ← Database-Tool
│   ├── Tool_FileRW.log     ← FileRW-Tool
│   ├── Tool_MeinTool.log   ← Dein Tool!
│   └── ...
```

Die Zuordnung geschieht über den Namespace:
- `ClawdDotNet.Core.*` → `Core.log`
- `ClawdDotNet.Tools.{Name}.*` → `Tool_{Name}.log`
- `ClawdDotNet.Host.*` → `Host.log`

Log-Level: `Debug`, `Info`, `Warn`, `Error`
