Let troubleshoot see the machine and answer with a route

It could describe a conf edit but not the switch that makes it, and knew
nothing about containers, pools or the array it was reasoning about.
This commit is contained in:
Gmer4Lfe
2026-08-13 17:22:58 -04:00
parent b7df54e4f3
commit 57e2b0d670
3 changed files with 112 additions and 4 deletions
+22
View File
@@ -403,6 +403,16 @@ if ($diagnostic) {
}
}
// The machine itself, separate from the AI subsystem's own health above. Read from the cache the
// WebGUI already writes, so it costs a file read rather than a docker ps and a disk sweep.
if ($can('system_state')) {
$state = vv_ai_system_state();
if ($state !== '') {
$attached['system'] = 'machine state';
$diagBlock .= $state;
}
}
// The troubleshooting profile gets the actual tail of the one log the operator is looking at,
// warnings and ordinary lines alike. The fleet-wide WARN/ERROR sweep above cannot answer "why
// did this one stop" — the last line a script printed before dying is usually not labelled.
@@ -628,6 +638,18 @@ if ($profile === 'chat') {
. "which line shows it. 'This looks like a Varaverk problem' with nothing behind it "
. "sends someone hunting through code they did not write and cannot fix, which is "
. "the least useful place you can send them.\n\n"
. "TELL THEM WHERE TO CLICK, NOT WHICH FILE TO EDIT\n"
. "You cannot change any setting yourself in this mode, and that is the point: the "
. "fix is theirs to make. Nearly every setting has a control in the web UI, and the "
. "passages include a map of where each one lives — use it. Give the route the way "
. "you would say it out loud: which tab, which card, which control.\n\n"
. " Daily container updates are off now. Scheduler tab → Daily Sync Maintenance → "
. "Steps → toggle Docker Update off.\n\n"
. "Name the conf key as well, so they can confirm they changed the right thing — but "
. "the route comes first and the file is the footnote. Telling someone to edit "
. "master.conf over SSH when a switch exists is worse advice: it is slower, it skips "
. "the backup and the syntax check that the UI write does for them, and it is how a "
. "typo takes the array down. Say a setting has no control only when the map says so.\n\n"
. "Do not suggest editing conf files by hand. Settings on this page have controls, "
. "and the operator is reading this inside the WebGUI. Name the control.\n\n"
. "IF IT REALLY IS A DEFECT, FILE IT\n"
+81
View File
@@ -1372,6 +1372,87 @@ function vv_ai_bug_report(array $b): string {
return $out;
}
// The machine as it is right now — hardware, containers, storage — summarised small enough to
// sit in a prompt.
//
// Read from the cache the WebGUI already writes every minute rather than measured here. A
// question must not cost a docker ps across fifty containers plus a disk sweep; the numbers move
// on their own schedule and one minute stale is the same answer. The age is stated so the model
// can say "a minute ago" rather than implying it looked just now.
//
// Strictly read-only, and there is no counterpart that changes any of it. Knowing a container is
// down is what lets an explanation be about this machine instead of about Unraid in general;
// restarting it is a decision that belongs to a person looking at the screen.
function vv_ai_system_state(): string {
$p = '/tmp/varaverk/api/monitor.json';
if (!is_readable($p)) return '';
$d = json_decode((string) @file_get_contents($p), true);
if (!is_array($d)) return '';
$age = time() - (int) @filemtime($p);
$s = "LIVE MACHINE STATE (read-only, measured " . ($age < 90 ? 'under a minute' : round($age / 60) . ' minutes')
. " ago — you cannot change any of it)\n";
$sys = $d['system'] ?? [];
if ($sys) {
$s .= '- host: ' . ($sys['cpu_model'] ?? '?') . ', ' . ($sys['cpu_threads'] ?? '?') . " threads\n";
}
if (!empty($d['cpu']['overall'])) $s .= '- cpu: ' . $d['cpu']['overall'] . "% overall\n";
$r = $d['resources'] ?? [];
if (!empty($r['ram_total_mb'])) {
$used = (int) $r['ram_total_mb'] - (int) ($r['ram_free_mb'] ?? 0);
$s .= sprintf("- memory: %d of %d MB used\n", $used, (int) $r['ram_total_mb']);
}
foreach ((array) ($d['gpus'] ?? []) as $g) {
$s .= sprintf("- gpu %d: %s, %d/%d MB, %d%% util, %d°C\n", $g['index'] ?? 0, $g['name'] ?? '?',
$g['memory_used'] ?? 0, $g['memory_total'] ?? 0, $g['utilization'] ?? 0,
$g['temperature'] ?? 0);
}
// Named individually only when something is wrong with them. Fifty healthy container names is
// the bulk of this block and none of it is ever the answer; the handful that are not are.
//
// Taken from the payload's own `stopped` list rather than derived. There is no state field on
// a container row — only `status`, which reads "Up 15 hours (healthy)" — so an earlier version
// of this looked for "run", found it nowhere, and reported all fifty as down. A block that
// confidently lists healthy containers as stopped is worse than no block: it is exactly the
// sort of thing the model would build a whole wrong explanation on.
$ctrs = (array) ($d['containers'] ?? []);
$stopped = (array) ($d['stopped'] ?? []);
$down = array_values(array_filter(array_map(
fn($c) => trim((string) (is_array($c) ? ($c['name'] ?? '') : $c)), $stopped)));
// Up, but failing its own healthcheck. Running is not the same as working, and this is the
// state that produces "it is started, so why is nothing happening".
$sick = [];
foreach ($ctrs as $c) {
if (stripos((string) ($c['status'] ?? ''), 'unhealthy') !== false) $sick[] = (string) ($c['name'] ?? '?');
}
$s .= '- containers: ' . count($ctrs) . ' running';
$s .= $down ? ', STOPPED: ' . implode(', ', array_slice($down, 0, 12)) : ', none stopped';
$s .= $sick ? '; UNHEALTHY: ' . implode(', ', array_slice($sick, 0, 8)) : '';
$s .= "\n";
foreach ((array) ($d['storage'] ?? []) as $pool) {
if (empty($pool['name'])) continue;
$s .= sprintf("- pool %s: %s%% used\n", $pool['name'], $pool['used_pct'] ?? $pool['pct'] ?? '?');
}
$par = $d['parity'] ?? [];
if ($par) {
$s .= '- array: ' . (($par['num_missing'] ?? 0) ? $par['num_missing'] . ' disks MISSING' : 'no missing disks')
. (($par['num_disabled'] ?? 0) ? ', ' . $par['num_disabled'] . ' disabled' : '')
. (!empty($par['in_progress']) ? ', parity operation in progress' : '') . "\n";
}
if (!empty($d['ups']['available'])) {
$s .= '- ups: ' . ($d['ups']['status'] ?? '?') . "\n";
}
return $s . "\n";
}
// Where a report can go from this install, resolved once so the page and the sender agree.
//
// Local is opt-in and off by default, because a default that files into the operator's own
+9 -4
View File
@@ -113,7 +113,11 @@ const VV_AI_PROFILES_DEF = [
'hint' => 'Reasons from evidence — the log you have open, or one you name. Says so when there is none.',
'turns' => 2,
'ui' => true,
'caps' => ['retrieve', 'health', 'run_evidence', 'scoped_log', 'incidents', 'conf_lookup', 'file_bugs'],
// system_state is read-only and shared with repair. Both need to know a container is down
// or a pool is full to explain anything about this machine rather than about Unraid in
// general; neither gets a way to act on it, and only repair can change a setting.
'caps' => ['retrieve', 'health', 'system_state', 'run_evidence', 'scoped_log',
'incidents', 'conf_lookup', 'file_bugs'],
],
// The only profile that may change a setting, and the only one not offered as a button.
//
@@ -132,8 +136,8 @@ const VV_AI_PROFILES_DEF = [
'hint' => 'Works through a finding with you, and can apply a fix you approve.',
'turns' => 3,
'ui' => false,
'caps' => ['retrieve', 'health', 'run_evidence', 'scoped_log', 'incidents', 'conf_lookup',
'conf_write', 'probe', 'file_findings', 'phrasebook', 'past_fixes'],
'caps' => ['retrieve', 'health', 'system_state', 'run_evidence', 'scoped_log', 'incidents',
'conf_lookup', 'conf_write', 'probe', 'file_findings', 'phrasebook', 'past_fixes'],
],
];
@@ -142,7 +146,8 @@ const VV_AI_PROFILES_DEF = [
const VV_AI_CAP_MEANING = [
'retrieve' => 'retrieval passages from the documentation index',
'kind_filter' => 'the retrieval kind filter the page exposes',
'health' => 'live health sweep measured at question time',
'health' => 'live health sweep measured at question time — the AI subsystem only',
'system_state' => 'read-only view of the machine: hardware, containers, pools, array, UPS',
'run_evidence' => 'run record and log tail for a script named in the question',
'scoped_log' => 'log tail for whatever the operator currently has open',
'incidents' => 'operator-written history of what previously went wrong with this thing',