Das kanonische komplexe Plugin. Ende-zu-Ende durchgespielt.

Jedes Konzept aus dem Rest dieser Dokumentation — der Boot-und-Load-Flow, die vier Hook-Muster, Layout-REGISTRY-Hooks, plugin-isolierte Migrationen, der indirekte Übersetzungs-Flow, die vier Lifecycle-Zustände, das Test-Suite-pro-Plugin-Muster — wird in storage/app/plugins/acelle/ai/ in der Praxis durchgespielt. Das Plugin liefert acht Eloquent-Modelle, vierzehn Migrationen, achtzehn Locales, über sechzig Blade-Templates, drei universelle anonyme Components, jeden Layout-Injection-Hook und über hundert Pest-Tests in seiner eigenen Test-Suite aus. Diese Seite ist das Lese-Rezept — was zuerst, was zuletzt anschauen und wie man daraus lernt, ohne sich in der produktionsreifen Verkabelung zu verlieren.

Warum dieses Plugin die kanonische Referenz ist

Die meisten Plugins in Produktion sind klein. Ein Sending Driver ist eine Klasse plus ein Connection-Blade. Eine Payment Gateway ist ein Service plus ein Redirect-Controller. Ein einfaches Sidebar-Add-on ist ein Hook::add-Aufruf. Keines davon übt das gesamte Plugin SDK aus — und kleine Plugins sind nicht die richtige Leseübung für einen Autor, der verstehen will, wie groß ein Plugin verantwortungsvoll werden kann.

acelle/ai liegt am anderen Ende des Spektrums. Es ist ein self-contained AI-Subsystem: eine Agent-Chatbox, universelle Text-Rewrite-Components, KB-grounded Coach-Personas und ein Admin-Observability-Dashboard. Das Aktivieren des Plugins in einer AcelleMail-Installation fügt die gesamte AI-Oberfläche hinzu, ohne Kern-Code anzufassen; das Deaktivieren entfernt sie sauber. Das Plugin nutzt jedes in dieser Dokumentation behandelte Konzept in Produktion. Es zu lesen ist der schnellste Weg, zu sehen, wie sich die Muster kombinieren, wenn das Feature nicht-trivial ist.

Der Dateibaum im Überblick

Aus dem README des Plugins:

OrdnerWas er besitzt
src/AIHandler/AI-Runtime-Engine — Engines, Agent-Loop, Tools, Settings-Resolver, Observability-Writer/Reader, KB-Lookup, URL-Sanitiser.
src/Models/Acht Eloquent-Modelle für das Audit-Substrat (AIConversation, AIMessage, AIRequest, AIToolCall, AIFeedback, AIRawBlob, AIDailyRollup, AIToolUndoRecord).
src/Controllers/Admin-Controller (/rui/admin/ai-*) + Public-API-Controller (/api/v1/ai/*) + der Plugin-Landing-PluginDashboardController.
src/Services/PluginStatusReport, AISettingsService, AutomationService, AIObservabilityPolicy usw.
src/ServiceProvider.phpAlleiniger Einstiegspunkt — registriert PSR-4, Routen, Views, Sprachdateien, Hooks, Middleware-Aliases, Lifecycle-Listener.
database/migrations/Vierzehn Audit-Substrat-Migrationen. Laufen auf activate, werden auf delete zurückgerollt.
resources/views/Über sechzig Admin-Blade-Templates + drei universelle anonyme Components (<x-mc-ai-chatbox>, <x-mc-ai-rewrite>, <x-mc-ai-subject-ab-generator>) + Chatbox- / Sparkle-JS-Partials.
resources/assets/CSS (~14 Dateien) + JS (~21 Dateien), publiziert nach public/plugins/acelle/ai/ über vendor:publish --tag=plugin --force bei Plugin-Installation.
resources/lang/Achtzehn Locales × neun Sprachdateien = die gesamte Oberfläche des AI-Moduls übersetzt.
tests/Über hundert Pest-Tests (Feature + Unit) + die Basisklasse Acelle\Ai\Tests\PluginTestCase.
routes.phpPlugin-Routen (Admin + Public API + das Plugin-Landing-Dashboard unter /plugins/acelle/ai/dashboard).
composer.jsonPlugin-Metadaten; extra.setting-route zeigt auf PluginDashboardController@index, sodass der „Settings"-Button auf der Admin-Plugins-Seite tief in das eigene Dashboard des Plugins verlinkt.

Keiner dieser Ordner ist bespoke. Jeder mappt direkt auf einen Abschnitt im Rest dieser Dokumentation. Das Plugin zu lesen ist ein Prozess des Wiedererkennens desselben Musters, ausgeliefert im Maßstab.

Acht Eloquent-Modelle — das Audit-Substrat

Die Datenschicht des AI-Moduls ist um Auditierbarkeit herum geformt: Jede Konversation, jeder Modell-Aufruf, jeder Tool-Call, jedes Nutzer-Feedback und jeder Blob roher Provider-Outputs wird für Replay und Observability erfasst. Acht Modelle decken dieses Substrat ab:

ModellTabelleWas es repräsentiert
AIConversationai_conversationsEine Zeile pro Multi-Turn-Agent- / Support-Session. Trägt Customer- + User-FKs, Task-Key, Screen-Route und aufgerollte Token- / Cost-Totals.
AIMessageai_messagesEine Zeile pro Nutzer- / Agent-Turn. Rolle, Content-JSON, FK auf einen Tool-Call, Latenz, verwendetes Modell.
AIRequestai_requestsEine Zeile pro Upstream-API-Aufruf. Engine, Prompt-Hash, Latenz, Kosten, Fehlerstatus. Verbindet AIMessage mit dem tatsächlichen HTTP-Traffic.
AIToolCallai_tool_callsFunction-Call-Invocations, die von einem Agent-Turn ausgehen. Tool-Name, Input/Output-JSON, Source-Flag.
AIFeedbackai_feedbackDaumen hoch/runter + Freitext-Feedback pro Message + pro Konversation.
AIRawBlobai_raw_blobsOriginale rohe Provider-Antworten, für Replay / Audit aufbewahrt. Separate Tabelle, weil die Rollup-Tabelle klein bleiben muss.
AIDailyRollupai_daily_rollupTagesaggregat für das Admin-Observability-Dashboard — Token-Totals, Kosten, Fehlerrate. Vor-aggregiert, damit das Dashboard günstig liest.
AIToolUndoRecordai_tool_undo_recordsVerfolgt umkehrbare Tool-Aktionen für die Funktion „Letzten Schritt rückgängig machen".

Drei Muster aus dieser Liste übertragen sich direkt auf andere Plugins. „Rohe Provider-Antworten" in eine separate Tabelle von „aufgerollter Zusammenfassung" zu splitten, lässt die Rollup-Tabelle klein genug bleiben, um sie zu scannen. Nullable FKs auf customers und users lassen dieselbe Zeile für authentifizierten und anonymen Traffic funktionieren. Eine-Zeile-pro-Tag-Rollups geben dem Admin-Dashboard günstige Reads ohne schweren JOIN gegen die Activity-Tabellen.

Vierzehn Migrationen, je eine Zeile

Die Migrations-Dateinamen in storage/app/plugins/acelle/ai/database/migrations/ erzählen ihre eigene Geschichte — additiv über die Zeit, sofort umkehrbar, niemals ein destruktiver Schema-Move:

DateinameWas sie tut
2026_04_28_000001_create_ai_conversations_table.phpMulti-Turn-Chat-Sessions — uid, customer_id FK, Status-Enum, Token-/Cost-Rollups
2026_04_28_000002_create_ai_messages_table.phpEinzelner Nutzer- / Agent-Turn — Rolle, Content-JSON, Tool-Call-FK, Latenz, verwendetes Modell
2026_04_28_000003_create_ai_requests_table.phpEine Zeile pro Upstream-API-Aufruf — Engine, Prompt-Hash, Latenz, Kosten, Fehler
2026_04_28_000004_create_ai_tool_calls_table.phpFunction-Call-Invocations aus einem Agent-Turn — Input / Output JSON
2026_04_28_000005_create_ai_feedback_table.phpDaumen hoch/runter + Freitext-Feedback pro Message + pro Konversation
2026_04_28_000006_create_ai_raw_blobs_table.phpOriginale rohe Provider-Antworten, für Replay / Audit aufbewahrt
2026_04_28_000007_create_ai_daily_rollup_table.phpTagesaggregat für das Admin-Dashboard — Token-Totals, Kosten, Fehlerrate
2026_04_29_000001_add_client_message_id_to_ai_messages.phpCross-Tab-Dedup-Spalte — additiv, keine Defaults
2026_04_30_000002_add_source_to_ai_tool_calls.phpVerfolgt, ob ein Tool-Call aus der Agent- vs. Support-Route kam
2026_05_02_180000_widen_ai_conversations_client_session_uid.phpULID- / UUID-Breiten-Fix — Spalten-ändernde Migration, vollständig umkehrbar
2026_05_02_200000_create_ai_tool_undo_records_table.phpVerfolgt umkehrbare Tool-Aktionen für die „Undo last"-Funktion
2026_05_03_000001_add_url_sanitization_to_ai_requests.phpFügt eine JSON-Spalte für sanitierte URL-Telemetrie hinzu
2026_05_04_000001_create_ai_settings_table.phpPlugin-Level-Admin-Settings — bewusst getrennt von plugins.data, sodass jede Zeile indizierbar ist

Lesen Sie die Migrationen oben-nach-unten, und Sie haben die Schema-Evolution des gesamten AI-Moduls. Jede additive Migration ist ein Feature-Ship — die additive Form ist es, was den delete_plugin_*-Rollback des Plugins immer funktionieren lässt, selbst wenn ein Admin nach einem Jahr Feature-Akkretion deinstalliert.

Jeder Hook, den das Plugin verwendet

Der ServiceProvider des Plugins übt jedes Hook-Muster außer FILTER aus. Ein Grep nach Hook::* gegen storage/app/plugins/acelle/ai/src/ServiceProvider.php:

REGISTRY (Hook::add) — sechs Beiträge

  • Neun add_translation_file-Einträge bei register() Zeilen 175-197 — einer pro Übersetzungs-Oberfläche (rewrite, chatbox, prompts, wait, subject AB, settings, admin usage, audit, permissions). Jede Oberfläche ist eine separat editierbare Datei in der Admin-Languages-UI.
  • layout.head.assets bei boot() Zeile 688 — trägt Chatbox-CSS + JS zu jeder Seite bei, die die Master-app- / admin-Layouts erweitert.
  • layout.body.before_close bei boot() Zeile 699 — trägt das Chatbox-Bubble-HTML und den Sparkle-Popover vor jedem </body> bei.
  • admin.sidebar.groups bei boot() Zeile 718 — fügt die AI-Gruppe mit ihren drei oder vier Child-Links zur Admin-Sidebar hinzu.

Alle sechs sichern sich mit aiPluginAvailable() ab — einem Helper, der letztlich zu Plugin::getByName('acelle/ai')->isActive() auflöst. null zurückzugeben, wenn das Plugin abgeschaltet ist, ist die konventionelle Art, sauber zu verschwinden, ohne den Service Provider zu entladen.

EVENT (Hook::on) — zwei Lifecycle-Listener

  • activate_plugin_acelle/ai bei Zeile 472 — führt artisan migrate gegen den Migrations-Ordner des Plugins aus.
  • delete_plugin_acelle/ai bei Zeile 546 — akzeptiert das $keepData-Flag, rollt Migrationen zurück, wenn nicht gesetzt, behält die Audit-Tabellen, wenn gesetzt.

BEHAVIOR (Hook::set) — ein Icon-URL-Override

  • icon_url_acelle/ai bei Zeile 522 — überschreibt den per-Plugin BEHAVIOR-Hook, sodass die Admin-Plugins-Seite das Icon des AI-Moduls statt der Default-plugin.svg des Hosts rendert.

Zusammen sind diese die meiste Hook-Oberfläche des Plugins. Das ServiceProvider.php oben-nach-unten zu lesen, ist der schnellste Weg, zu sehen, wie sich die Muster in Produktion kombinieren.

Die Chatbox-UI-Oberfläche

Das Plugin trägt drei universelle Blade-Components in resources/views/components/ bei — keiner davon benötigt, dass die Host-Anwendung von ihrer Existenz weiß:

  • <x-mc-ai-chatbox> — das schwebende Chatbox-Bubble, das eine Multi-Turn-Agent-Konversation öffnet. Über den REGISTRY-Hook layout.body.before_close gemountet, sodass es auf jeder app- + admin-Seite erscheint.
  • <x-mc-ai-rewrite> — universelle „Text umschreiben"-Affordance, die neben jede Textarea im Host gesetzt werden kann. Plugin-namespaced, keine zentrale Registrierung.
  • <x-mc-ai-subject-ab-generator> — generiert A/B-Subject-Line-Varianten aus einem Prompt. Im Kampagnen-Editor verwendet.

Diese drei Components zeigen das Muster für „Plugin trägt UI zu mehreren Host-Seiten bei, ohne jede Seite zu forken": Liefern Sie die Component als anonyme Blade-Component unter dem View-Namespace Ihres Plugins aus, registrieren Sie sie über die Layout-REGISTRY-Hooks für globales Mounting, oder lassen Sie Host-Seiten per Opt-in einbinden. Beide Muster funktionieren; das AI-Plugin nutzt beide.

Neun Dateien × achtzehn Locales

Der register() des Plugins registriert neun separate Übersetzungsdateien. Die Language::dump()-Maschinerie materialisiert dann jede in siebzehn nicht-englische Locale-Laufzeitordner unter storage/app/data/plugins/acelle/ai/lang/. Das Ergebnis auf der Platte: 153 Laufzeit-Übersetzungsdateien (9 Dateien × 17 nicht-englische Locales + 9 englische Originale = 162 minus die 9 englischen Master = 153 Dump-Clones), jede separat editierbar über die Languages-Admin-UI des Hosts.

Die neun Oberflächen-Dateien (registriert über die $aiLangFiles-Schleife in ServiceProvider::register()):

  • ai_rewrite — die universelle Text-Rewrite-Component
  • ai_chatbox — die Chatbox-UI
  • ai_chatbox_prompts — vordefinierte Prompts, die in der Chatbox angezeigt werden
  • ai_chatbox_wait — die Smart-Wait-UI („nachschlagen… Tool ausführen… Antwort verfassen")
  • ai_subject_ab — der Subject-A/B-Generator
  • ai_settings — Admin-Settings-Seitenbeschriftungen
  • admin_ai_usage — Admin-Usage- / Kosten-Dashboard
  • admin_ai_audit — Admin-Audit- / Replay-UI
  • admin_ai_permissions — Admin-Per-Feature-Berechtigungs-Toggles

Dieser Split ist die praktische Antwort auf „Wie halte ich Übersetzungsdateien klein genug, dass ein Übersetzer eine in einer Sitzung bearbeiten kann": ein Master pro logischer Oberfläche, separat registriert, pro Locale materialisiert, unabhängig über die Admin-UI bearbeitet.

Plugin-eigene Config-Dateien

Das Plugin besitzt zwei Config-Dateien unter config/ — registriert in ServiceProvider::boot() über $this->mergeConfigFrom() und über die Standard-Helper config('ai.*') und config('ai-navigation-hints.*') erreichbar. Plugin-eigene Config ist der richtige Ort für statische Metadaten, die sich pro Installation nicht ändern (Engine-Katalog, Prompt-Templates, Navigation-Defaults); admin-editierbare Settings leben in der von der Migration geseedeten ai_settings-Tabelle.

Der Split — Config für plugin-ausgelieferte unveränderliche Defaults, DB-Zeilen für admin-editierbare veränderliche Settings — ist ein Muster, das sauber auf andere Plugins portiert. Beides in die plugins.data-JSON-Spalte zu legen, ist verlockend, bestraft aber die Performance der Admin-UI; eine dedizierte indizierte Tabelle war die richtige Wahl.

Die Test-Infrastruktur

Das Test-Verzeichnis des Plugins folgt der tests/-Form des Hosts — Unit- + Feature-Ordner plus eine PluginTestCase-Basisklasse im Root:

storage/app/plugins/acelle/ai/tests/
├── PluginTestCase.php          ← seeds the plugin row as active before every test
├── Feature/
│   ├── AIHandler/              ← engines, agent loop, tools, observability writer
│   └── PluginLifecycle/        ← lifecycle integration tests
├── Unit/                       ← isolated unit tests (no Laravel boot for some)
├── Fixtures/                   ← test fixtures + factories
├── Snapshots/                  ← Pest snapshot artefacts
└── Support/                    ← test-only helpers

Die phpunit.xml des Hosts registriert die Test-Suite des Plugins als <testsuite name="Plugin: acelle/ai">. ./vendor/bin/pest --testsuite="Plugin: acelle/ai" führt die volle Suite parallel zu den Unit- + Feature-Suites des Hosts aus.

Die PluginTestCase im Root demonstriert die per-Request-Gate-Cache-Reset-Falle, die jedes Plugin, das Middleware ausliefert, adoptieren sollte — siehe den Testing § Gate-Cache-Falle für das vollständige Muster. Ohne sie beobachtet der zweite Test in der Suite und folgende veralteten Cache-Zustand aus dem Boot des ersten Tests.

Wie man daraus lernt

Jede Zeile eines Plugins mit hunderttausend Token zu lesen, ist nicht die richtige Übung. Ein Vier-Schritt-Rezept ist nützlicher:

  1. Klonen oder symlinken Sie das Plugin in eine Host-Installation. Aktivieren Sie es. Öffnen Sie die Admin-Sidebar — die AI-Gruppe sollte erscheinen. Klicken Sie „Settings" auf dem Eintrag der Admin-Plugins-Seite — das Plugin-Landing-Dashboard sollte laden. Das beweist, dass die UI-Oberfläche des Plugins in Ihren lokalen Host verdrahtet ist.
  2. Lesen Sie src/ServiceProvider.php oben-nach-unten. Vierzig Minuten. Jedes Konzept aus Plugin-Architektur + Hook-System + UI-Injection + Übersetzungen erscheint in dieser einen Datei im Produktions-Maßstab. Verweisen Sie nebenher auf die Deep-Dive-Seiten.
  3. Verfolgen Sie ein Feature Ende-zu-Ende. Wählen Sie das Chatbox-Bubble. Finden Sie den Einstiegspunkt (Hook layout.body.before_close im ServiceProvider), folgen Sie dem Partial, das er zurückgibt (ai::partials.body_assets), finden Sie die anonyme Component, die das Bubble rendert, finden Sie das JS, das es mountet, finden Sie die API-Route, die das JS aufruft, finden Sie den Controller, folgen Sie ihm bis zur AIHandler-Runtime-Engine, beobachten Sie, wie die Zeilen AIConversation + AIMessage + AIRequest eingefügt werden. Zwei bis drei Stunden, Ende-zu-Ende. Danach wissen Sie, welche Muster das AI-Plugin verwendet und welche nicht.
  4. Adaptieren Sie ein Muster für Ihr eigenes Plugin. Wählen Sie das kleinste, das mappt — meist die Per-Übersetzungs-Oberfläche-Registrierungs-Schleife, die Admin-Sidebar-Gruppe oder der Per-Tag-Rollup-Tabellen-Ansatz. Schneiden Sie den AI-spezifischen Code heraus; behalten Sie das strukturelle Muster. Das ist der Day-One-Produktivitätsgewinn des Plugin-Autors.

Wie es weitergeht

Das ist die letzte der elf Entwickler-Deep-Dives. Von hier aus ist der Index-Hub die natürliche Zusammenfassung: Dokumentations-Index zeigt jede Seite im Cluster, organisiert in Foundation / Building / Quality / Reference. Die Entwickler-Landing ist der marketing-förmige Einstiegspunkt für neue Besucher aus der Suche.

Zwei praktische nächste Schritte, wenn Sie bereit sind, ein echtes Plugin auszuliefern: der activate → test → delete-Zyklus aus dem Testing-Deep-Dive beweist, dass der delete_plugin_*-Hook-Listener Ihres Plugins sauber aufräumt. Plugin-Architektur § Wiederherstellung aus fehlerhaftem Zustand ist die Seite zum Lesezeichen für produktive Runtime-Issues — drei Fehlermodi plus der exakte Fix-Pfad für jeden.

Über AcelleMail-spezifische Muster hinaus gilt das breitere Laravel-Ökosystem. Plugin-Code ist vanilla Laravel; der Host-Runtime-Loader ist das einzige nicht-standardisierte Stück. Alles, was Sie über Eloquent, Blade, Pest, Queues, Scheduling oder Middleware wissen, funktioniert unverändert innerhalb des Plugin-Ordners.