Add a troubleshooting profile with the scoped log, and a deterministic conf-key lookup

This commit is contained in:
Gmer4Lfe
2026-08-05 20:18:05 -04:00
parent 185abdb442
commit 5a813eb4c8
4 changed files with 151 additions and 10 deletions
+74
View File
@@ -707,6 +707,80 @@ function vv_ai_token_stats(): array {
return $out;
}
// ── Scoped lookups for the WebGUI dock ───────────────────────────────────────────────────────
// The tail of one named script's log, for the troubleshooting profile. vv_ai_recent_logs()
// answers "is anything wrong anywhere"; this answers "why did THIS fail", which is a different
// question and needs the ordinary lines too, not just WARN and ERROR — the last thing a script
// printed before stopping is usually not labelled as a warning.
//
// Contained to LOG_DIR by realpath. The id arrives from a request, and a log path that escaped
// would read arbitrary files into a model's context.
function vv_ai_scoped_log(string $id, int $lines = 120): array {
$rel = preg_replace('/\.sh$/', '', trim($id)) . '.log';
if ($rel === '' || str_contains($rel, "\0")) return ['ok' => false, 'error' => 'bad id'];
$base = realpath(LOG_DIR);
$path = realpath(LOG_DIR . '/' . $rel);
if ($base === false || $path === false) return ['ok' => false, 'error' => 'no log yet'];
if (!str_starts_with($path, $base . '/')) return ['ok' => false, 'error' => 'outside log dir'];
if (!is_file($path)) return ['ok' => false, 'error' => 'no log yet'];
$all = @file($path, FILE_IGNORE_NEW_LINES) ?: [];
return [
'ok' => true,
'path' => $rel,
'total' => count($all),
'mtime' => @filemtime($path) ?: null,
'tail' => array_slice($all, -max(10, min($lines, 400))),
];
}
// Where a conf key actually lives. Deterministic on purpose: the model should narrate this, not
// work it out. Searches the host's own conf and master, reports file, line and value.
//
// This is the "Jonny" case — someone is sure a setting is in master.conf and it is really in the
// host conf. Failing to find it is the unhelpful answer; finding it and saying where is the
// useful one, and neither should depend on the model guessing which file to trust.
function vv_ai_find_conf_key(string $key): array {
if (!preg_match('/^[A-Za-z][A-Za-z0-9_]{1,63}$/', $key)) return ['ok' => false];
$me = vv_detect_host();
$order = array_values(array_unique(array_filter([
$me !== 'unknown' ? $me . '.conf' : null,
'master.conf',
])));
foreach (vv_get_conf_files() as $f) if (!in_array($f, $order, true)) $order[] = $f;
// Two passes, active before commented, across every file — not first-match-wins per line.
// These confs document each setting in a comment block above it, and those blocks contain
// lines like "# RSYNC_ENABLED=false → ALL rsync stops everywhere". A single pass matched
// that prose at line 529 and reported the setting as commented out while the live value sat
// active at line 546. Reporting an enabled setting as disabled is the exact failure this
// lookup exists to prevent, so the active definition always wins.
$q = preg_quote($key, '/');
$files = [];
foreach ($order as $file) $files[$file] = @file(CONF_DIR . '/' . $file, FILE_IGNORE_NEW_LINES) ?: [];
foreach ($files as $file => $lines) {
foreach ($lines as $i => $line) {
if (preg_match('/^\s*' . $q . '\s*=/', $line)) {
return ['ok' => true, 'file' => $file, 'line' => $i + 1,
'text' => trim($line), 'commented' => false];
}
}
}
foreach ($files as $file => $lines) {
foreach ($lines as $i => $line) {
if (preg_match('/^\s*#\s*' . $q . '\s*=/', $line)) {
return ['ok' => true, 'file' => $file, 'line' => $i + 1,
'text' => trim($line), 'commented' => true];
}
}
}
return ['ok' => false];
}
function vv_ai_job_dir(): string {
if (!is_dir(VV_AI_JOB_DIR)) @mkdir(VV_AI_JOB_DIR, 0700, true);
return VV_AI_JOB_DIR;