diff --git a/compose.yaml b/compose.yaml index c73e7c5..ceb67fc 100644 --- a/compose.yaml +++ b/compose.yaml @@ -714,7 +714,7 @@ services: build: context: . dockerfile: platform/openwebui/Dockerfile - image: ${OPENWEBUI_IMAGE:-mike-ai/openwebui:main-01f4282-agent-loop-v8} + image: ${OPENWEBUI_IMAGE:-mike-ai/openwebui:main-01f4282-agent-loop-v9} container_name: mike-ai-open-webui restart: unless-stopped volumes: diff --git a/config/hermes-mcp-core.yaml.example b/config/hermes-mcp-core.yaml.example new file mode 100644 index 0000000..db98e67 --- /dev/null +++ b/config/hermes-mcp-core.yaml.example @@ -0,0 +1,21 @@ +# Merge this block into ~/.hermes/config.yaml on any VPN-connected client. +# Hermes discovers the tools from each HTTP MCP server automatically at startup. +mcp_servers: + athena_operator: + url: "http://192.168.1.212:8202/mcp" + timeout: 3600 + connect_timeout: 30 + enabled: true + supports_parallel_tool_calls: false + web: + url: "http://192.168.1.212:8203/mcp" + timeout: 180 + connect_timeout: 30 + enabled: true + supports_parallel_tool_calls: true + athena_context: + url: "http://192.168.1.212:8201/mcp" + timeout: 120 + connect_timeout: 30 + enabled: true + supports_parallel_tool_calls: true diff --git a/dev/test_openwebui_filters.py b/dev/test_openwebui_filters.py index 2eb1bdb..43bbd61 100644 --- a/dev/test_openwebui_filters.py +++ b/dev/test_openwebui_filters.py @@ -289,6 +289,56 @@ class AutoToolSelectorTests(unittest.IsolatedAsyncioTestCase): self.assertIn("search_web/fetch_url", result["messages"][0]["content"]) self.assertEqual(result["reasoning_effort"], "medium") + async def test_unraid_media_write_gets_portable_operator_and_web(self): + result = await self._select( + "Ermittle online das neueste Video, lade es mit yt-dlp auf dem Unraid-Host " + "in einen temporären Ordner, konvertiere es mit ffmpeg und lege die fertige " + "Datei im Share Transfer ab." + ) + self.assertEqual( + result["tool_ids"], + [ + "server:mcp:athena-operator-local", + "server:mcp:mua-readonly-local", + "server:mcp:mua", + "server:mcp:web-general-local", + ], + ) + self.assertIn("direct MUA management or shell tool", result["messages"][0]["content"]) + self.assertIn("Do not ask the user to enable another tool", result["messages"][0]["content"]) + self.assertIn("start-status-result pattern", result["messages"][0]["content"]) + self.assertTrue(result["metadata"]["mikeai_long_operator_task"]) + + async def test_generic_remote_host_file_operation_gets_operator(self): + result = await self._select( + "Führe auf dem Server ein vorhandenes Skript aus und speichere die neue Datei unter /data/export." + ) + self.assertEqual(result["tool_ids"], ["server:mcp:athena-operator-local"]) + self.assertTrue(result["metadata"]["mikeai_long_operator_task"]) + + async def test_readonly_diagnostics_do_not_get_long_operator_budget(self): + result = await self._select("Prüfe nur lesend den Zustand von Unraid.") + self.assertNotIn("mikeai_long_operator_task", result["metadata"]) + + async def test_narrow_safety_clause_does_not_cancel_authorized_unraid_write(self): + result = await self._select( + "Lade das Video auf dem Unraid-Host herunter, konvertiere es und lege es " + "im Transfer-Share ab. Installiere dabei kein Paket dauerhaft und ändere " + "keine Unraid-Systemkonfiguration." + ) + self.assertEqual( + result["tool_ids"], + [ + "server:mcp:athena-operator-local", + "server:mcp:mua-readonly-local", + "server:mcp:mua", + ], + ) + + async def test_plain_download_advice_does_not_attach_operator(self): + result = await self._select("Erkläre mir, wie ein Browser einen Download technisch durchführt.") + self.assertNotIn("tool_ids", result) + async def test_voice_transcription_variants_select_unraid(self): result = await self._select( "Welche Dacher Contäner laufen aktuell auf dem Anrate Server?" diff --git a/docs/CLIENT_TOOL_STANDARD.md b/docs/CLIENT_TOOL_STANDARD.md new file mode 100644 index 0000000..161e6fd --- /dev/null +++ b/docs/CLIENT_TOOL_STANDARD.md @@ -0,0 +1,64 @@ +# Client-unabhängiger Werkzeugstandard + +## Ziel + +Eine Aufgabe darf nicht nur deshalb scheitern, weil sie in Hermes oder Pi statt +in OpenWebUI gestellt wurde. OpenWebUI-Filter sind eine Komfort- und +Kontextoptimierung, aber niemals die eigentliche Berechtigungs- oder +Fähigkeitsschicht. + +## Kernfähigkeiten für jeden vertrauenswürdigen VPN-Client + +| Fähigkeit | MCP-Endpunkt | Zweck | +|---|---|---| +| Breiter Operator | `http://192.168.1.212:8202/mcp` | Terminal, Dateien, Docker, Git, Downloads, Konvertierung, APIs und SSH zu konfigurierten Systemen | +| Allgemeines Web | `http://192.168.1.212:8203/mcp` | Site-unabhängige Suche und Seitenabruf | +| Plattformwissen | `http://192.168.1.212:8201/mcp` | Aufbau, Ist-Zustand, Quellen und Änderungsablauf von Athena | + +Diese drei Server bilden das tragfähige Minimum. Fach-MCPs wie Home Assistant, +MUA, ARR, Navidrome und GitHub ergänzen kurze strukturierte Operationen. Sie +sind der bevorzugte Weg, aber keine Voraussetzung: Fehlt eine Spezialfunktion, +bleiben allgemeines Web und Operator verfügbar. + +## Client-Verhalten + +- **OpenWebUI:** Native Websuche bleibt grundsätzlich verfügbar. Der Auto Tool + Selector hängt Fach-MCPs und den Operator anhand allgemeiner + Fähigkeitsklassen an. Allgemeine Hostarbeit wird anhand von Ausführungs- oder + Änderungsabsicht plus Host-, Datei-, Kommando- oder Dienstkontext erkannt; + nicht anhand einzelner Programme oder Websites. +- **Hermes:** Die drei Kernendpunkte werden in `~/.hermes/config.yaml` + eingetragen. Hermes verbindet sie beim Start, ruft MCP `tools/list` auf und + stellt die entdeckten Werkzeuge in jedem Gespräch bereit. Vorlage: + `config/hermes-mcp-core.yaml.example`. +- **Pi und weitere MCP-Clients:** Dieselben Streamable-HTTP-Endpunkte direkt + konfigurieren. Es ist kein OpenWebUI-Filter und kein zusätzlicher Proxy + erforderlich. + +## Verbindliche Sicherheitsgrenze + +Toolbeschreibungen und Systemprompts helfen dem Modell bei der Wahl, sind aber +keine Sicherheitsgrenze. Unverzichtbare Verbote, Ausgabelimits, +Schreibabläufe und Schutz vor dem Verlust der Remote-Erreichbarkeit werden im +MCP beziehungsweise im Athena-Operator-Dienst erzwungen. Dadurch gelten sie +identisch für OpenWebUI, Hermes, Pi und zukünftige Clients. + +## Lange operative Aufgaben + +Lange Arbeiten verwenden clientunabhängig das Muster +`Start -> kompakter Status -> Ergebnis/Verifikation`. Recherche wird vor der +Mutation abgeschlossen; Hilfsmittel werden gebündelt vorbereitet und lange +Kommandos asynchron gestartet, wenn ein synchroner Werkzeugaufruf in ein +Zeitlimit laufen könnte. OpenWebUI gewährt dieser allgemeinen Aufgabenklasse +ein höheres, aber weiterhin endliches Ausführungsbudget: 64 Aufrufe insgesamt +und 24 je Werkzeug. Normale Recherche bleibt bei 40 beziehungsweise 12. Das +ist keine Sonderregel für YouTube, ffmpeg, MUA oder einen bestimmten Server. + +## Community-Bezug + +Das folgt dem MCP-Modell: Ein Client verbindet einen vertrauenswürdigen +Streamable-HTTP-Server und entdeckt dessen Werkzeuge über `tools/list`. +Hermes registriert konfigurierte HTTP-MCPs beim Start als normale Werkzeuge; +OpenWebUI registriert Remote-MCPs global und kann sie pro Anfrage über +`tool_ids` aktivieren. Clientseitige Auswahl optimiert Kontext und Bedienung, +serverseitige Regeln bleiben maßgeblich. diff --git a/docs/COMPONENTS.md b/docs/COMPONENTS.md index c4802a9..3795605 100644 --- a/docs/COMPONENTS.md +++ b/docs/COMPONENTS.md @@ -16,7 +16,7 @@ | Platform Context MCP | Athena-/MikeAI-Wissen, begrenzter Laufzeitsnapshot und kontrollierte Dokumentationspflege | eigener Container ohne Docker-Socket, Shell, Egress oder Secrets | Kern | | Athena Operator MCP | Entwicklung und vollständiger Betrieb der KI-Plattform | strukturierte Operationen plus breites, begrenztes Terminal; Erreichbarkeitsänderungen blockiert | Kern | | Operator-Kontext | `docs/QWEN_OPERATOR_CONTEXT.md` plus `config/operator-system-prompt.txt` | versionierte Selbstbeschreibung und Sicherheitsregeln für Qwen | Kern | -| Unraid/MUA | MUA r020+ auf dem HomeServer, direkter MCP-Endpunkt | read-only Automatik; begrenzte Datei-/Medieninventare; Verwaltung bei explizitem Änderungsauftrag; idempotente Batch-Updates | Kern | +| Unraid/MUA | MUA r022+ auf dem HomeServer, direkter MCP-Endpunkt | read-only Automatik; begrenzte Datei-/Medieninventare; Verwaltung bei explizitem Änderungsauftrag; idempotente Batch-Updates; asynchrone Jobs mit Start/Status/Aufräumen für lange Arbeiten | Kern | | Whisper | ggml-org/whisper.cpp | Service im Router-Deploy | optional | | XTTS-v2 | Coqui, offizielles CUDA-12.1-Image per Digest | RTX-3060-Container, Stimme `Annmarie Nele`, CPML | Kern | | TTS-Gateway | `platform/docker/tts-gateway/` | interne Queue, stabile deutsche Satzblöcke, automatische Erkennung rein englischer Texte und Piper-Fallback | Kern | diff --git a/docs/CURRENT_REFERENCE.md b/docs/CURRENT_REFERENCE.md index 4468836..504a09d 100644 --- a/docs/CURRENT_REFERENCE.md +++ b/docs/CURRENT_REFERENCE.md @@ -197,6 +197,21 @@ aktiviert er zusätzlich die native Websuche. Der Ablauf steht in `docs/UNRAID_MEDIA_AUDIT_WORKFLOW.md`. Der frühere GraphQL-basierte Unraid-MCP wurde entfernt und gehört weder zum Start noch zum Recovery. +Ab MUA r022 stehen für lange, explizit autorisierte Arbeiten zusätzlich +`unraid_system_job_start`, `unraid_system_job_status` und +`unraid_system_job_cleanup` bereit. Der Start kehrt sofort mit einer Job-ID +zurück; Statusabfragen liefern nur kompakte, redigierte Ausgaben. Damit blockiert +ein Download, Transcode oder vergleichbarer Auftrag weder Open WebUI noch Hermes +oder Pi bis zum Prozessende. Die drei Werkzeuge erben dieselbe ausdrücklich +erteilte Berechtigung wie die uneingeschränkte Shell. + +Auto Tool Selector 4.5 kennzeichnet allgemeine lange Operator-Aufgaben +produktunabhängig. Der OpenWebUI-Agentenloop stellt dafür bis zu 64 +Werkzeugausführungen insgesamt und 24 je Werkzeug bereit; normale Aufgaben +bleiben bei 40/12. Zusätzlich verlangt der Systemhinweis das generische +Start-Status-Ergebnis-Muster und reserviert den Abschluss für Verifikation, +Zielablage und Aufräumen. + Der GitHub-Container läuft produktiv. Token-Datei, interner Streamable-HTTP-Handshake, fehlende Host-Portfreigabe und exakt drei read-only Werkzeuge wurden am 24. August 2026 verifiziert. @@ -204,14 +219,15 @@ Die Transportbrücke verwendet den OpenWebUI-kompatiblen `mcp-proxy` 0.12.0 im stateless Betrieb. Supergateway wurde nach reproduzierbaren HTTP-400-Fehlern bei `notifications/initialized` aus diesem Pfad entfernt. -Die agentische OpenWebUI-Schleife führt höchstens 40 einzelne Werkzeuge aus, -höchstens zwölf Aufrufe desselben Werkzeugnamens und höchstens zweimal exakt -dieselbe Signatur. Nach Ende des Budgets stehen zusätzliche interne Runden ausschließlich -für eine sichtbare werkzeugfreie Schlussantwort bereit. Das produktive -OpenWebUI-Derivat trägt den Tag -`mike-ai/openwebui:main-01f4282-agent-loop-v8`. +Die agentische OpenWebUI-Schleife führt bei normalen Aufgaben höchstens 40 +einzelne Werkzeuge und höchstens zwölf Aufrufe desselben Werkzeugnamens aus. +Allgemeine lange Operator-Aufgaben erhalten 64 beziehungsweise 24. Exakt +dieselbe Signatur bleibt stets auf zwei Wiederholungen begrenzt. Nach Ende des +Budgets stehen zusätzliche interne Runden ausschließlich für eine sichtbare +werkzeugfreie Schlussantwort bereit. Das produktive OpenWebUI-Derivat trägt +den Tag `mike-ai/openwebui:main-01f4282-agent-loop-v9`. -Das in V8 enthaltene Verhalten aus V7 begrenzt zusätzlich die +Das in V9 enthaltene Verhalten aus V8 begrenzt zusätzlich die Werkzeugantworten in OpenWebUIs internen Fortsetzungsrunden auf 12.000 Zeichen je Ergebnis und 64.000 Zeichen pro Antwortlauf. Damit greift die Begrenzung auch bei Ergebnissen, die erst nach dem ersten Request entstehen. @@ -238,6 +254,22 @@ Angebote und sortierte Notebook, Zubehör sowie ein Ersatzteilgerät aus. Das belegt sowohl die technische Grenze als auch eine brauchbare Synthese; ein eBay-spezifischer MCP war nicht erforderlich. +Auto Tool Selector 4.5 erkennt zusätzlich allgemeine operative Arbeit über +Fähigkeitsklassen: Eine ausdrückliche Ausführungs- oder Änderungsabsicht in +Verbindung mit Host, Dateisystem, Kommando, Dienst, Pfad oder typischen +Kommandozeilenwerkzeugen stellt den Athena Operator bereit. Dadurch benötigen +neue Programme wie Download- oder Medienwerkzeuge keine eigene Selector-Regel. +Bei Unraid-Arbeit werden MUA für kompakte Bestandsaufnahme und der Operator für +die ausdrücklich verlangte allgemeine Schreibarbeit gemeinsam angeboten. +Lange Operator-Aufgaben werden dabei produktunabhängig markiert und folgen dem +Start-Status-Ergebnis-Muster, damit Recherche und Vorbereitung nicht das +Budget für Ausführung, Verifikation und Aufräumen verbrauchen. + +Für andere Clients gilt `docs/CLIENT_TOOL_STANDARD.md`. Hermes lädt die drei +Kern-MCPs Web, Operator und Plattformwissen direkt per Streamable HTTP; die +Vorlage liegt unter `config/hermes-mcp-core.yaml.example`. Damit hängt die +Fähigkeit nicht vom OpenWebUI-Filter ab. + Der Platform Context MCP läuft ohne Docker-Socket, Shell, Egress oder Secrets. Ein root-eigener Minutentimer erzeugt nur einen begrenzten Laufzeitsnapshot. Der Schreibpfad ist auf `docs/*.md`, Vorschau, ausdrückliche Freigabe, atomare diff --git a/docs/TOOL_ARCHITECTURE_2026-08-24.md b/docs/TOOL_ARCHITECTURE_2026-08-24.md index 280bcfd..84605f5 100644 --- a/docs/TOOL_ARCHITECTURE_2026-08-24.md +++ b/docs/TOOL_ARCHITECTURE_2026-08-24.md @@ -31,6 +31,13 @@ bereit und hängt anhand der Anfrage passende Fachkataloge an. Er verweigert keine Fähigkeit und erfordert keine Site-spezifischen Regeln. Alle zentralen MCPs sind über feste WireGuard-Ports auch für Hermes und Pi erreichbar. +Der verbindliche client-unabhängige Mindeststandard ist in +`docs/CLIENT_TOOL_STANDARD.md` festgelegt: allgemeines Web, Athena Operator und +Plattformwissen werden in jedem vertrauenswürdigen VPN-Client konfiguriert. +OpenWebUI darf diese Grundfähigkeiten zur Kontextoptimierung automatisch +auswählen; Hermes und Pi entdecken sie direkt über die MCP-Endpunkte. Die +Sicherheitsgrenze liegt immer serverseitig und hängt nicht von einem Filter ab. + Marketplace-Recherche bleibt ebenfalls allgemein: Der Selector erkennt Kauf-, Angebots-, Preis- und Versandsuchen unabhängig von einer einzelnen Website. Qwen beginnt mit einer fokussierten Suche, nutzt höchstens zwei diff --git a/platform/openwebui/filters/auto_tool_selector.py b/platform/openwebui/filters/auto_tool_selector.py index b022c30..0e4f797 100644 --- a/platform/openwebui/filters/auto_tool_selector.py +++ b/platform/openwebui/filters/auto_tool_selector.py @@ -1,7 +1,7 @@ """ title: MikeAI Auto Tool Selector author: MikeAI -version: 4.3.0 +version: 4.5.0 description: Keeps broad web access available and adds relevant portable MCP domains without acting as a gate. """ @@ -109,6 +109,48 @@ class Filter: ) return search_intent and marketplace_context + def _is_explicitly_readonly(self, raw_text: str) -> bool: + """Recognise a global read-only request, not a narrow safety exception.""" + text = self._normalise(raw_text) + return self._matches( + text, + ( + r"\b(?:nur|ausschlie(?:ß|ss)lich|lediglich)\s+(?:lesen|pr[uü]fen|anzeigen|auflisten)\b", + r"\bread[- ]only\b", + r"\bohne\s+(?:downloads?|umbenennungen?|schreibzugriff|[aä]nderungen?)\b", + r"\bohne\s+downloads?\b.{0,80}\b(?:[aä]nderungen?|umbenennungen?)\b", + r"\b(?:nichts|keinerlei)\s+(?:ver[aä]ndern|[aä]ndern|schreiben)\b", + r"\b(?:[aä]ndere|ver[aä]ndere)\s+nichts\b", + ), + ) + + def _needs_general_operator(self, raw_text: str) -> bool: + """Detect broad host work by capability, not product or website name.""" + text = self._normalise(raw_text) + if self._is_explicitly_readonly(raw_text): + return False + action_intent = self._matches( + text, + ( + r"\b(?:ausf[uü]hr|starte?|stoppe?|restart|installier|deploy|bau|erstell|" + r"schreib|speicher|leg|kopier|verschieb|umbenenn|l[oö]sch|entfern|" + r"download|herunterlad|konvertier|transkodier|komprimier|extrahier|" + r"bearbeit|[aä]nder|modifizier|aktualisier|reparier|fix)\w*\b", + r"\b(?:f[uü]hre|lass)\b.{0,80}\b(?:befehl|kommando|skript|command)\b", + ), + ) + operational_context = self._matches( + text, + ( + r"\b(?:host|server|unraid|unreid|unrate|anrate|nas|linux|debian|docker|" + r"container|terminal|shell|kommando|befehl|datei(?:system)?|ordner|" + r"verzeichnis|pfad|share|freigabe|repository|repo|mcp|api)\b", + r"\b(?:ffmpeg|ffprobe|yt-dlp|youtube-dl|curl|wget|git|docker\s+compose)\b", + r"(?:^|\s)/(?:tmp|data|mnt|opt|srv|var|etc|home)(?:/|\b)", + ), + ) + return action_intent and operational_context + @staticmethod def _add_system_rule( body: dict, @@ -129,7 +171,25 @@ class Filter: "the Athena Operator terminal when it can answer the unresolved question. Never " "duplicate an existing backend; integrate or relay it and report any remaining gap." ) - if "unraid" in selected and "unraid_admin" in selected: + if "unraid" in selected and "unraid_admin" in selected and "operator" in selected: + rule += ( + " This request explicitly authorizes general work on Unraid. Use " + "read-only MUA first for compact evidence, then use the direct MUA " + "management or shell tool for commands and writes on Unraid. Use " + "the Athena Operator only for orchestration on Athena or as a " + "fallback when the target MCP cannot perform the required operation; " + "do not spend repeated calls inventing a second SSH path. Do not ask " + "the user to enable another tool: all three capabilities are attached. " + "Do not install persistent packages unless explicitly requested; " + "prefer an existing binary or task-local temporary copy. Avoid " + "overwrites unless authorized and verify the final artifact." + " For long-running work, finish discovery before mutation, then use " + "a generic start-status-result pattern: prepare prerequisites in as few " + "calls as possible, start the long command asynchronously when the tool " + "timeout might be exceeded, poll only compact progress, and reserve " + "enough calls for verification, final placement, and cleanup." + ) + elif "unraid" in selected and "unraid_admin" in selected: rule += ( " This request explicitly asks for an Unraid state change. Use the " "read-only MUA tools to establish evidence, then the narrowest MUA " @@ -140,6 +200,19 @@ class Filter: "Do not ask the user to enable another tool: both required MUA " "capabilities are already attached to this request." ) + elif "unraid" in selected and "operator" in selected: + rule += ( + " This request requires general filesystem, command, download, " + "conversion or other host work on Unraid. Use read-only MUA first " + "for compact inventory and runtime evidence, then use the Athena " + "Operator terminal for the exact user-authorized command sequence. " + "Do not ask the user to enable another tool: both capabilities are " + "already attached. Do not install persistent packages unless the " + "user explicitly requested installation; prefer an existing binary " + "or a task-local temporary copy. Write only to the requested target, " + "avoid overwrites unless explicitly authorized, and verify the final " + "artifact independently before reporting success." + ) elif "unraid" in selected: rule += ( " For bounded file or media-library inventory on Unraid, prefer " @@ -193,7 +266,8 @@ class Filter: selected: list[str] = [] - operator = self._matches( + general_operator = self._needs_general_operator(raw_text) + operator = general_operator or self._matches( text, ( r"\bathena[- ]operator\b", @@ -260,25 +334,19 @@ class Filter: unraid_write = unraid and self._matches( text, ( - r"\b(?:update|updates|aktualisier\w*|upgrade\w*)\b.*\b(?:docker|cont[aä]iner|image|unraid)\b", - r"\b(?:docker|cont[aä]iner|image|unraid)\b.*\b(?:update|updates|aktualisier\w*|upgrade\w*)\b", - r"\b(?:start|stop|restart|starte|stoppe|beende|rebuild|reinitialisier|neu\s+erstell)\w*\b.*\bcont[aä]iner\b", - r"\bcont[aä]iner\b.*\b(?:start|stop|restart|starte|stoppe|beende|rebuild|reinitialisier|neu\s+erstell)\w*\b", - r"\b(?:installier|entfern|l[oö]sch|[aä]nder|modifizier)\w*\b.*\b(?:unraid|docker|cont[aä]iner)\b", - r"\b(?:unraid|docker|cont[aä]iner)\b.*\b(?:installier|entfern|l[oö]sch|[aä]nder|modifizier)\w*\b", - r"\bf[uü]hr\w*\b.*\bupdates?\b.*\b(?:durch|aus)\b", + r"\b(?:update|updates|aktualisier\w*|upgrade\w*)\b.*\b(?:docker|cont[aä]iner|image|unraid)\b", + r"\b(?:docker|cont[aä]iner|image|unraid)\b.*\b(?:update|updates|aktualisier\w*|upgrade\w*)\b", + r"\b(?:start|stop|restart|starte|stoppe|beende|rebuild|reinitialisier|neu\s+erstell)\w*\b.*\bcont[aä]iner\b", + r"\bcont[aä]iner\b.*\b(?:start|stop|restart|starte|stoppe|beende|rebuild|reinitialisier|neu\s+erstell)\w*\b", + r"\b(?:installier|entfern|l[oö]sch|[aä]nder|modifizier)\w*\b.*\b(?:unraid|docker|cont[aä]iner)\b", + r"\b(?:unraid|docker|cont[aä]iner)\b.*\b(?:installier|entfern|l[oö]sch|[aä]nder|modifizier)\w*\b", + r"\bf[uü]hr\w*\b.*\bupdates?\b.*\b(?:durch|aus)\b", + r"\b(?:lad(?:e|en)?|download|herunterlad|konvertier|transkodier|kopier|verschieb|speicher|schreib|leg|umbenenn|l[oö]sch)\w*\b.{0,160}\b(?:unraid|unreid|unrate|anrate|nas|share|freigabe)\b", + r"\b(?:unraid|unreid|unrate|anrate|nas|share|freigabe)\b.{0,160}\b(?:lad(?:e|en)?|download|herunterlad|konvertier|transkodier|kopier|verschieb|speicher|schreib|leg|umbenenn|l[oö]sch)\w*\b", + r"\b(?:kommando|befehl|shell[- ]befehl)\w*\b.{0,100}\b(?:auf|in)\b.{0,30}\b(?:unraid|unreid|unrate|anrate|nas)\b", ), ) - if unraid_write and self._matches( - text, - ( - r"\b(?:nur|lediglich|ausschlie(?:ß|ss)lich)\b.{0,30}\b(?:pr[uü]f|anzeig|auflist|status|nachseh)\w*\b", - r"\b(?:keine|keinerlei|nichts?)\b.{0,40}\b(?:[aä]nder|update|aktualisier|durchf[uü]hr|installier|download|umbenenn)\w*\b", - r"\bohne\b.{0,50}\b(?:[aä]nder|update|aktualisier|durchf[uü]hr|installier|download|umbenenn)\w*\b", - r"\b(?:nicht|nichts?)\b.{0,20}\b(?:durchf[uü]hr|ausf[uü]hr|aktualisier|installier)\w*\b", - r"\b(?:[aä]nder|erstell|installier|l[oö]sch|entfern|download|umbenenn)\w*\b.{0,25}\b(?:nichts?|keine[nrms]?|keinerlei)\b", - ), - ): + if unraid_write and self._is_explicitly_readonly(raw_text): unraid_write = False github = self._matches( text, @@ -365,6 +433,11 @@ class Filter: # Consumed by the versioned Open WebUI continuation patch. # This is a request-local execution policy, not a site rule. metadata["mikeai_marketplace_research"] = True + # Long operational tasks need more executions than bounded research. + # This is a capability-class policy, not a rule for any product, site, + # command or MCP. The patched Open WebUI loop consumes this marker. + if self._needs_general_operator(latest_text): + metadata["mikeai_long_operator_task"] = True if not selected: return body diff --git a/platform/openwebui/patch_tool_finalization.py b/platform/openwebui/patch_tool_finalization.py index c887e94..a8c13db 100644 --- a/platform/openwebui/patch_tool_finalization.py +++ b/platform/openwebui/patch_tool_finalization.py @@ -31,8 +31,14 @@ replacement = """ tool_call_iterations = 0 # Per-tool and exact-repeat limits below prevent one low-level MCP # operation from consuming the whole turn. tool_call_executions = 0 - max_tool_call_executions = 40 - max_executions_per_tool = 12 + long_operator_task = bool( + (metadata or {}).get('mikeai_long_operator_task', False) + ) + # Research stays tightly bounded. Explicit long-running host work + # receives a larger, still finite budget so discovery cannot consume + # the calls needed for execution, verification and cleanup. + max_tool_call_executions = 64 if long_operator_task else 40 + max_executions_per_tool = 24 if long_operator_task else 12 # Inlet filters only see the first request, not later internal # tool continuations. Bound the evidence injected into those # continuations so one broad inventory cannot consume context.