From a04368e9fc218ec1855b33b667a98377d4c36fb8 Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Wed, 12 Aug 2026 18:08:50 -0400 Subject: [PATCH] Notice when the question is about their own machine The other detector recognises named components, so its complement is unbounded; this one reads grammar instead, which is not. --- Deployment/master.conf.template | 13 +++++++ Plugin/unraid/Tools/ai_chat_worker.php | 2 +- Plugin/unraid/include/ai.php | 52 ++++++++++++++++++++++++++ Plugin/unraid/pages/readme/ui-map.md | 10 +++++ 4 files changed, 76 insertions(+), 1 deletion(-) diff --git a/Deployment/master.conf.template b/Deployment/master.conf.template index bc9911d..4eece09 100644 --- a/Deployment/master.conf.template +++ b/Deployment/master.conf.template @@ -1909,6 +1909,19 @@ AI_WEB_SEARCH_RESULTS=4 AI_WEB_SEARCH_TIMEOUT=6 +# ━━━ AI Chat Caution ━━━ +# General Chat cannot see this installation, and web search gives it confident-sounding material +# about the outside world. The danger is the overlap: a question about THIS machine, answered +# from a general page about Unraid, reads exactly like an answer about this machine. +# +# Detection is grammatical rather than topical — possessives, "this box", state questions, "what +# happened last night" — because the topics are unbounded and the grammar is not. When it fires, +# chat keeps its caution and defers instead of answering from the web. +# +# Pipe-separated extra phrases for what grammar misses: a nickname for the box, a share name, +# anything that in practice means "mine". Matched as literal text, not as patterns. + AI_CHAT_MY_SYSTEM_PHRASES="" + # ━━━ AI Conf Write Access ━━━ # Separate switch from AI_ENABLED, off by default, and an explicit key whitelist. Never paths, # never credentials, never a container name. An empty whitelist means no writes regardless of diff --git a/Plugin/unraid/Tools/ai_chat_worker.php b/Plugin/unraid/Tools/ai_chat_worker.php index 3387662..df5231e 100644 --- a/Plugin/unraid/Tools/ai_chat_worker.php +++ b/Plugin/unraid/Tools/ai_chat_worker.php @@ -855,7 +855,7 @@ if ($profile === 'chat' && vv_ai_chat_needs_varaverk($question)) { // // Deterministic on both sides: this fires only when detection says there is nothing Varaverk in // the question, so the cautious branch above keeps every question it would have caught. -} elseif ($profile === 'chat' && $webHave) { +} elseif ($profile === 'chat' && $webHave && !vv_ai_asks_about_this_system($question)) { // Removes a false prohibition. It does NOT classify the question, and the first version of // this did: it told the model that nothing named a Varaverk component so this was a question // about the wider world. That inference is not the detector's to make. The detector finds diff --git a/Plugin/unraid/include/ai.php b/Plugin/unraid/include/ai.php index a968d0d..9291152 100644 --- a/Plugin/unraid/include/ai.php +++ b/Plugin/unraid/include/ai.php @@ -112,6 +112,58 @@ function vv_ai_chat_needs_varaverk(string $question): bool { || vv_ai_resolve_run_target($question) !== ''; } +// Is the operator asking about THEIR OWN machine, without naming anything on it? +// +// A different axis from vv_ai_chat_needs_varaverk(), and the reason that one cannot be extended +// to cover this. That detector recognises Varaverk-shaped tokens — a .sh file, a SCREAMING_CASE +// key, a run target — so its complement is every question that names none, which is unbounded. +// "why is my array so slow tonight", "how much space do I have left" and "is everything healthy" +// are all about this machine and contain nothing to detect. Enumerating that class topic by topic +// is endless; the operator referring to their own system is not, because it is grammar rather +// than subject matter. Possessives, this-machine deictics, state questions and questions about +// what happened here cover most of it in about a dozen patterns. +// +// Used to decide when NOT to relax the chat profile's caution. Wrong in the safe direction: a +// false positive costs a redirect to a profile that can actually see the machine, a false +// negative risks a general web page answered as though it described this installation. +function vv_ai_asks_about_this_system(string $question): bool { + $q = ' ' . mb_strtolower(trim($question)) . ' '; + + $builtin = [ + // Theirs, or the box in front of them. + '/\b(my|our|mine)\b/', + '/\bthis (box|server|machine|system|install(ation)?|setup|rig|host|array|pool|cache|node)\b/', + // State of something here. "is everything healthy", "are the containers up". + '/\b(is|are|was|were)\b[^.?!]{0,40}\b(health(y|ier)?|ok|okay|fine|up|down|running|stopped|' + . 'slow|fast|full|empty|broken|failing|stuck|degraded|offline|online)\b/', + // What happened here, and when. + '/\bwhat (happened|went wrong|is (going on|happening))\b/', + '/\b(last night|this morning|yesterday|today|tonight|right now|currently|at the moment)\b/', + // Capacity and load, which are only ever about the machine being asked from. + '/\bhow (much|many)\b[^.?!]{0,30}\b(space|room|ram|memory|disk|storage|free|left|used)\b/', + '/\bdo i have\b/', + // Ownership of running things. + '/\b(my|the) (containers?|dockers?|shares?|disks?|drives?|pools?|arrays?)\b/', + ]; + foreach ($builtin as $re) if (preg_match($re, $q)) return true; + + // Operator-supplied phrases, for the ones grammar does not reach — a nickname for the box, a + // share name, whatever turns up in practice. Pipe-separated rather than a conf array because + // vv_conf_vars() reads scalars and hands back "(" for an array, and this needs to be readable + // at question time from a settings field the operator can edit. + // + // Matched as literal text, never as patterns. The field is editable from the settings UI, and + // a regex arriving from there would run against every question asked. + $extra = trim((string) (vv_conf_vars()['AI_CHAT_MY_SYSTEM_PHRASES'] ?? '')); + if ($extra !== '') { + foreach (explode('|', $extra) as $phrase) { + $phrase = mb_strtolower(trim($phrase)); + if ($phrase !== '' && mb_strpos($q, $phrase) !== false) return true; + } + } + return false; +} + // Asking for a script to be written, as opposed to asking about one that exists. The verb is // required: "what does arr_sync.sh do" names a script and wants documentation, "write me a script // that does X" names none and wants code. Getting that backwards in either direction is the whole diff --git a/Plugin/unraid/pages/readme/ui-map.md b/Plugin/unraid/pages/readme/ui-map.md index 8c51430..aea5354 100644 --- a/Plugin/unraid/pages/readme/ui-map.md +++ b/Plugin/unraid/pages/readme/ui-map.md @@ -434,6 +434,16 @@ Saved into `host1.conf`, which does not need to be opened by hand. |---|---|---|---| | `HOST1_ZFS_REPORT_IGNORE_POOLS` | a list, one entry per line | in this section | — | +## AI Chat Caution + +Route: AI tab → Settings → Configuration → *AI Chat Caution* + +Saved into `master.conf`, which does not need to be opened by hand. + +| Setting | Control | Where | What it does | +|---|---|---|---| +| `AI_CHAT_MY_SYSTEM_PHRASES` | a text box | in this section | Pipe-separated extra phrases for what grammar misses: a nickname for the box, a share name, anything that in practice means "mine". Matched as literal text, not as patterns. | + ## AI Conf Write Access Route: AI tab → Settings → Configuration → *AI Conf Write Access*