Offer Troubleshoot as a profile, and say plainly when it has no log

This commit is contained in:
Gmer4Lfe
2026-08-09 00:25:52 -04:00
parent 073352b21e
commit 37784bce6c
5 changed files with 97 additions and 8 deletions
+3 -2
View File
@@ -69,7 +69,7 @@ buries it. `--kind=readme` is the hard filter for that case:
bash AI/ai_query.sh --kind=readme "what is Varaverk"
```
`--kind` filters on where a chunk came from — `header`, `readme`, `manual`, `template`, `doc`
`--kind` filters on where a chunk came from — `header`, `readme`, `manual`, `template`, `doc`, `ui`
and composes with `--section`. Prefer it over `--section` for "what is" and "why does this
exist" questions, where the answer is narrative rather than a header field.
@@ -109,6 +109,7 @@ Roughly 2,900 chunks across ~180 files:
| `manual` | Every `Manual-*.md` |
| `template` | `Deployment/*.template` — the versioned conf schema |
| `doc` | Top-level `README.md`, `Manual.md`, design notes |
| `ui` | `Plugin/unraid/pages/readme/*.md` — the WebGUI's own help panels |
**Script bodies are not indexed.** Headers state intent, code states mechanism; for the
questions this answers, intent retrieves better and costs far less.
@@ -239,7 +240,7 @@ dock.
| `varaverk` | 3 | yes | answers only from the index, with citations. The default. |
| `chat` | 8 | **no** | ordinary conversation. Holds zero capabilities, deliberately. |
| `code` | 4 | no | drafts shell for Custom Scripts; scans its own output for destructive ops |
| `troubleshoot` | 2 | yes | reasons from an open log first, docs second. May file bug reports. |
| `troubleshoot` | 2 | yes | reasons from evidence — an open log, or one you name. May file bug reports. |
Capabilities are granted per profile — retrieval, live health, run evidence, scoped log,
incidents, conf lookup, bug filing, code scanning. `chat` holding an empty list is a guarantee,
+20 -2
View File
@@ -444,9 +444,27 @@ if ($profile === 'chat') {
// profile of its own. The assistant's contract is "answer only from the passages, refuse if
// absent" — exactly wrong here, where the evidence is a log that is not in the index and
// never should be. This one reasons from the log first and the documentation second.
// What it says it has is what it was actually given. This line used to assert a log tail
// unconditionally, which was safe only while the profile could be reached exclusively by
// opening one. It is now a button as well, so it is reachable with nothing attached — and
// telling a model it holds evidence it does not hold is precisely how you get a confident
// reading of a log that was never there, which is the one failure this profile exists to
// avoid. Stated from $attached, so the prompt cannot drift from the inputs.
$haveLog = isset($attached['log_tail']);
$system = "You are helping the operator work out why something on their Varaverk server did "
. "not do what they expected. You have the tail of the relevant log, live system "
. "state measured just now, and documentation passages about the scripts involved.\n\n"
. "not do what they expected. "
. ($haveLog
? "You have the tail of the relevant log, live system state measured just now, "
. "and documentation passages about the scripts involved.\n\n"
: "You have NOT been given a log for this question — none was open and none was "
. "named that resolves to one. You have live system state and documentation "
. "passages only.\n\n"
. "Say that plainly before anything else, and name what would fix it: open the "
. "log on the Scheduler tab, or name the script in the question. Do not describe "
. "what a log 'would' show, do not infer an outcome from the script's name, and "
. "never write that you are reading or checking a log — you have none to read. "
. "An invented reading is worse here than anywhere else, because the operator "
. "came to this profile specifically for evidence.\n\n")
. "HOW TO ANSWER\n"
. "Lead with what the log actually shows. Quote the line that matters. Then say what "
. "it means, using the documentation to explain what the script was trying to do.\n\n"
+57 -2
View File
@@ -238,6 +238,9 @@ function vv_ai_chat_assets(): void {
white-space:nowrap; flex:1; min-width:0; }
.vv-ai-crow.active .vv-ai-crow-t { color:#9bd; }
.vv-ai-crow-m { font-size:9px; color:#3a3a3a; font-family:monospace; flex-shrink:0; }
.vv-ai-chat { transition:height .14s ease; }
.vv-ai-grow { font-size:13px !important; line-height:1; padding:4px 9px !important; }
.vv-ai-grow-on { color:#9bd !important; border-color:#2d4a6a !important; }
.vv-ai-crow-s { font-size:9px; color:#5c7cfa; font-family:monospace; }
.vv-ai-c .vv-ai-crow-s { color:#7a9ae0; }
.vv-ai-crow-x { font-size:11px; color:#333; flex-shrink:0; padding:0 2px; visibility:hidden; }
@@ -652,6 +655,36 @@ vv_ai_profiles_script();
});
const newBtn = $('new'); if (newBtn) newBtn.addEventListener('click', newChat);
// ── Expand / collapse ────────────────────────────────────────────────
// Both heights come from the markup, so the page decides them and this only switches
// between them. Remembered per instance: the dashboard chat and the tab's are different
// sizes for different reasons, and a shared key would make one of them wrong.
//
// Scroll position is pinned to the bottom afterwards. Growing the box leaves the transcript
// scrolled where it was, which puts the newest answer off-screen at the exact moment you
// asked for more room to read it.
const growBtn = $('grow');
if (growBtn) {
const el = chatEl();
const base = el.dataset.h || '';
const tall = el.dataset.hTall || '';
const KEY = 'vv-ai-tall-' + P;
const apply = big => {
if (!base || !tall) return;
el.style.height = big ? tall : base;
growBtn.textContent = big ? '⤡' : '⤢';
growBtn.title = big ? 'Back to the smaller view' : 'Give the conversation more room';
growBtn.classList.toggle('vv-ai-grow-on', big);
scroll();
};
growBtn.addEventListener('click', () => {
const big = el.style.height !== tall;
localStorage.setItem(KEY, big ? '1' : '0');
apply(big);
});
if (localStorage.getItem(KEY) === '1') apply(true);
}
// Surface any script error into the transcript. Without it a throw anywhere on the page is
// invisible unless the console happens to be open, which is how a silent hang survives a
// diagnosis session.
@@ -759,7 +792,12 @@ vv_ai_profiles_script();
// profile which profile button starts active
// compact card-sized chrome, for a chat living inside a Monitor card
// height transcript height; a fixed value in compact mode, since a card in a grid row
// cannot grow with its content without dragging the row's other cards with it
// cannot grow with its content without dragging the row's other cards with it.
// Setting it also adds the expand control — a fixed height is exactly the situation
// where you sometimes want more room, and there is nothing to expand without one.
// tall the expanded height. Defaults to 2.5x height, which is the point where a long
// answer stops needing a scroll for most questions without the card swallowing the
// page it sits on
// empty empty-state text
// controls extra markup dropped into the composer's control strip
function vv_ai_chat_markup(string $prefix, array $o = []): void {
@@ -767,8 +805,21 @@ function vv_ai_chat_markup(string $prefix, array $o = []): void {
$compact = !empty($o['compact']);
$height = $o['height'] ?? '';
$empty = $o['empty'] ?? 'Ask Varaverk about itself.';
// 2.5x by default, computed here so the page can override with a value that suits its layout
// rather than the include guessing at one it cannot see.
$tall = $o['tall'] ?? '';
if ($height !== '' && $tall === '' && preg_match('/^(\d+(?:\.\d+)?)(px|vh|em|rem)$/', $height, $hm)) {
$n = (float)$hm[1] * 2.5;
// vh is a share of the viewport, so 2.5x runs off the bottom of the screen — an expand
// that puts the composer out of reach is worse than no expand. Clamped to something that
// still leaves the page scrollable to its own controls.
if ($hm[2] === 'vh') $n = min($n, 85);
$tall = rtrim(rtrim(number_format($n, 2, '.', ''), '0'), '.') . $hm[2];
}
$style = $height !== '' ? ' style="height:' . htmlspecialchars($height, ENT_QUOTES)
. ';max-height:none;"' : '';
. ';max-height:none;"'
. ' data-h="' . htmlspecialchars($height, ENT_QUOTES) . '"'
. ' data-h-tall="' . htmlspecialchars($tall, ENT_QUOTES) . '"' : '';
?>
<div class="vv-ai-chatwrap<?= $compact ? ' vv-ai-c' : '' ?>" style="display:flex;flex-direction:column;gap:<?= $compact ? '4px' : '12px' ?>;min-width:0;">
@@ -789,6 +840,10 @@ function vv_ai_chat_markup(string $prefix, array $o = []): void {
placeholder="<?= htmlspecialchars($o['placeholder'] ?? 'Ask anything — questions about this install route to the assistant on their own.', ENT_QUOTES) ?>"></textarea>
<div class="vv-ai-ctrls">
<?= $o['controls'] ?? '' ?>
<?php if ($height !== ''): ?>
<button class="vv-ai-btn ghost vv-ai-grow" id="<?= $p ?>-grow" type="button"
title="Give the conversation more room">⤢</button>
<?php endif; ?>
<button class="vv-ai-btn ghost" id="<?= $p ?>-new" type="button">New</button>
<span class="vv-ai-hint">Ctrl+Enter to send</span>
<button class="vv-ai-btn" id="<?= $p ?>-send" type="button">Ask</button>
+9 -2
View File
@@ -93,12 +93,19 @@ const VV_AI_PROFILES_DEF = [
'ui' => true,
'caps' => ['code_scan'],
],
// Offered as a button as well as entered by context. It was context-only on the reasoning
// that choosing it from a dashboard produces a troubleshooting contract with nothing to
// troubleshoot — true of the inputs, but not of the profile: name a script in the question
// and run_evidence resolves the run record and log for it without any scope at all. The
// prompt now states which of those it actually received rather than asserting a log, so
// picking it with nothing open degrades to an honest "I cannot see one" instead of an
// invented reading of a log that was never attached.
'troubleshoot' => [
'label' => 'Troubleshoot',
'short' => 'Troubleshoot',
'hint' => 'Reasons from the log you have open, then the docs. Files a bug only when the evidence shows one.',
'hint' => 'Reasons from evidence — the log you have open, or one you name. Says so when there is none.',
'turns' => 2,
'ui' => false,
'ui' => true,
'caps' => ['retrieve', 'health', 'run_evidence', 'scoped_log', 'incidents', 'conf_lookup', 'file_bugs'],
],
];
@@ -59,6 +59,8 @@ as the GPU and UPS cards.
| AI | Model residency, context size, VRAM, index size and staleness, tokens used today |
| Conversations | The last `$AI_CHAT_HISTORY_MAX` chats. Click to reopen, × to delete |
| Assistant | Ask a question without leaving the dashboard |
| ⤢ | Expands the conversation to about two and a half times its height. Remembered |
| Profile buttons | Which contract answers — General Chat, Varaverk Assistant, Code Sketcher, Troubleshoot |
---
@@ -93,6 +95,12 @@ Switching profile with the buttons keeps what is on screen but stops sending ear
model. Carrying cited, retrieval-grounded answers into a mode with no retrieval makes the model
keep referring to sources it can no longer see.
**Troubleshoot from here has no log open.** On the Scheduler tab it reads whatever log you have
in front of you; picked from the dashboard there is nothing attached, so it says so and tells you
how to give it evidence — open the log on the Scheduler tab, or name the script in the question,
which resolves that script's run record and log on its own. What it will not do is describe a log
it was never given.
## Conversations are shared with the AI tab
The same store backs both. Start a thread here, finish it on the AI tab, or the reverse — it is