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:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user