slash-commands
Slash-Commands
User-getriggerte Delegation an einen Assistant, direkt aus der Chat-Eingabe:
/gutscheine versandkostenfrei 10€
heisst: nimm assistants.gutscheine, gib ihm versandkostenfrei 10€ als Task,
zeig die Antwort im Chat. Das ist dieselbe Mechanik wie
invoke_assistant — nur vom Nutzer ausgeloest statt vom
Master-LLM.
Ein Command IST ein Assistant-Slug
Es gibt keine separate Command-Registry und kein zusaetzliches Config-Feld. Jeder Assistant des Tenants ist automatisch als Command aufrufbar, der Slug ist der Command-Name:
| Assistant-Slug | Command |
|---|---|
gutscheine |
/gutscheine |
kpi |
/kpi |
amazon_files |
/amazon_files |
Damit gibt es nur eine Quelle der Wahrheit: legt der Builder einen Assistant an, existiert der Command sofort mit — ohne zweiten Eintrag, der veralten koennte.
Nicht verwendet wird das Feld name — das ist der Anzeigename und enthaelt
Leerzeichen, Umlaute und Sonderzeichen (Amazon-Dateien auflisten,
Artikel & Preise), taugt also nicht als Command.
Ausfuehrung: direkt, ohne Master-LLM
Ein Slash-Command ueberspringt den Master-Turn komplett. Der Nutzer hat den Ziel-Assistenten explizit benannt — es gibt nichts zu entscheiden, also entscheidet auch kein LLM. Das spart einen Roundtrip und macht das Verhalten deterministisch.
Konsequenz: der Sub-Assistant sieht keinen Verlauf des Master-Chats. Er bekommt nur seinen eigenen System-Prompt und den Task. Ein Nachfassen ("und jetzt nochmal mit 15€") landet wieder beim Master-Assistant, nicht beim Sub-Assistant.
Verhalten im Detail
| Eingabe | Ergebnis |
|---|---|
/gutscheine versandkostenfrei 10€ |
Sub-Chat gegen gutscheine, Antwort im Master-Chat |
/gutscheine (ohne Argument) |
Greeting + suggestions/toggles/inputs des Assistenten |
/help |
Liste aller Commands des Tenants |
/gutschein 10€ (Tippfehler) |
Fehlermeldung mit Vorschlag /gutscheine |
reset das mal |
kein Command — geht normal ans LLM |
Guards
Striktes Matching. Nur ^/slug am Zeilenanfang, nur [a-z0-9_]. Kein
Fuzzy-Matching, kein Substring-Match. Historischer Hintergrund: der alte
Reset-Regex im ChatController matchte auf Substrings und loeschte bei
"reset das mal" ueberraschend Chats — er wurde am 2026-08-01 deaktiviert
(ChatController.php, Kommentar "Der Chat-Text darf nicht mehr als Trigger
dienen"). Diese Lehre gilt hier weiter.
Unbekannte Commands raten nicht. Trifft ein Slug keinen Assistant, kommt eine Fehlermeldung mit Aehnlichkeitsvorschlag — es wird nie ein anderer Assistant "hilfsweise" ausgefuehrt.
Uploads gewinnen. Eine Message mit Bild ist nie ein Command, auch wenn
der Text mit / beginnt.
In Sub-Chats deaktiviert. Dort ist die "User"-Message der Task eines
Master-Assistants, kein Nutzer-Tippen. Ein LLM, das seinen Task mit /
beginnt, loest keinen Command aus. Erkennung ueber den sub_-Prefix der
Chat-ID.
Tool-Allowlist. Fuer die Dauer des Commands wird die Allowlist des
ToolRunners eng auf invoke_assistant gesetzt und danach exakt
wiederhergestellt — auch wenn das Tool eine Exception wirft.
Recursion-Guard. Weil der Command ueber invoke_assistant laeuft, gilt
dieselbe MAX_DEPTH = 2 wie bei der LLM-getriebenen Delegation.
Autocomplete (nur Web)
Im internen Chat (webroot/js/chat-internal.js) oeffnet ein / am
Zeilenanfang ein Menue: Pfeiltasten navigieren, Enter/Tab uebernimmt, Escape
schliesst. Die Liste kommt von GET /chat/commands?tenant=X — also aus der
Tenant-Config, nicht aus einer im Frontend gepflegten Kopie.
Sobald ein Leerzeichen folgt, schliesst das Menue: ab da tippt der Nutzer sein Argument.
Nicht implementiert: Autocomplete im oeffentlichen Widget (chat.js), in
ChatInput.vue und in der Flutter-App. Dort funktionieren getippte Commands
trotzdem — nur ohne Menue. /chat/commands liefert anonymen Nutzern eine
leere Liste, weil Commands an interne Assistants delegieren.
Sichtbarkeit vs. Erreichbarkeit
hidden => true-Assistants (z.B. config_reviewer) tauchen nicht in
/help und nicht im Autocomplete auf, bleiben aber per Direkteingabe
aufrufbar. Die Liste ist Kuratierung, keine Zugriffskontrolle — der
eigentliche Schutz sind die Tool-Allowlists der Assistants.
Code
| Datei | Rolle |
|---|---|
src/Service/Chat/SlashCommandResolver.php |
Parsing, Aufloesung, /help-Rendering |
src/Service/Chat/SlashCommandRunner.php |
Ausfuehrung, Sink-Events, Fehlerbehandlung |
src/Service/Chat/ChatService.php |
Interception am Anfang von processMessage() |
src/Controller/ChatController.php |
commands()-Endpoint |
webroot/js/chat-internal.js |
Autocomplete-Menue |
Die Interception sitzt bewusst im ChatService, nicht im Controller — so
gilt sie fuer /chat (JSON) und /chat/stream (SSE) gleichermassen, ohne
Duplikat.
Tests
vendor/bin/phpunit tests/TestCase/Service/Chat/SlashCommandResolverTest.php
vendor/bin/phpunit tests/TestCase/Service/Chat/SlashCommandRunnerTest.php
vendor/bin/phpunit tests/TestCase/Service/Chat/ChatServiceTest.php
Verwandte Doks
- Agent-Delegation —
invoke_assistant+spawn_agent - Assistants konfigurieren — Slugs,
hidden, Tools