TaskMonkey Handbuch

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