A question about how a run went should arrive with the run attached, not with directions to the log panel
This commit is contained in:
@@ -736,6 +736,103 @@ function vv_ai_scoped_log(string $id, int $lines = 120): array {
|
||||
];
|
||||
}
|
||||
|
||||
// The run record a script's wrapper writes beside its log: how the last run ended, in four
|
||||
// fields, without reading a line of the log. "How did it go" is answerable from this alone —
|
||||
// status, exit code and the window it ran in — and the log tail then supplies the detail.
|
||||
// Separate from vv_ai_scoped_log() because they fail independently: a script killed mid-run
|
||||
// leaves a log and no record, and that difference is itself the answer.
|
||||
function vv_ai_run_record(string $id): array {
|
||||
$rel = preg_replace('/\.sh$/', '', trim($id)) . '.json';
|
||||
if ($rel === '' || str_contains($rel, "\0")) return ['ok' => false];
|
||||
|
||||
$base = realpath(LOG_DIR);
|
||||
$path = realpath(LOG_DIR . '/' . $rel);
|
||||
if ($base === false || $path === false) return ['ok' => false];
|
||||
if (!str_starts_with($path, $base . '/')) return ['ok' => false];
|
||||
|
||||
$r = json_decode((string)@file_get_contents($path), true);
|
||||
if (!is_array($r) || !isset($r['start'])) return ['ok' => false];
|
||||
|
||||
$start = (int)$r['start'];
|
||||
$end = isset($r['end']) ? (int)$r['end'] : 0;
|
||||
return [
|
||||
'ok' => true,
|
||||
'status' => (string)($r['status'] ?? '?'),
|
||||
'exit' => isset($r['exit']) ? (int)$r['exit'] : null,
|
||||
'start' => $start,
|
||||
'end' => $end ?: null,
|
||||
'duration' => $end > $start ? $end - $start : null,
|
||||
];
|
||||
}
|
||||
|
||||
// Which script a run-outcome question is about, when the operator names it in prose rather than
|
||||
// by opening its log. "How did the daily orch go" has to resolve to Orchestrators/daily_sync_
|
||||
// maintenance before anything can be attached to the context.
|
||||
//
|
||||
// Aliases are an explicit table, and matching against real log ids is exact on the underscore
|
||||
// tokens — no similarity scoring. The failure mode of a scored match here is attaching the wrong
|
||||
// script's log and answering confidently about a run the operator never asked about, which is
|
||||
// indistinguishable from a correct answer unless they already knew. Ambiguity returns nothing so
|
||||
// the question falls through to ordinary retrieval, which is merely unhelpful rather than wrong.
|
||||
function vv_ai_resolve_run_target(string $question): string {
|
||||
$q = strtolower($question);
|
||||
|
||||
static $aliases = [
|
||||
'Orchestrators/daily_sync_maintenance' => ['daily orch', 'daily orchestrator',
|
||||
'daily sync', 'daily maintenance', 'daily run'],
|
||||
'Orchestrators/weekly_sync_maintenance' => ['weekly orch', 'weekly orchestrator',
|
||||
'weekly sync', 'weekly maintenance', 'weekly run'],
|
||||
'Orchestrators/critical_sync_maintenance' => ['critical orch', 'critical sync', 'critical run'],
|
||||
'Orchestrators/intermediate_sync_maintenance' => ['intermediate orch', 'intermediate sync'],
|
||||
'Orchestrators/watchdog_orchestrator' => ['watchdog orch', 'watchdog orchestrator',
|
||||
'watchdogs'],
|
||||
'Orchestrators/transcode_management' => ['transcode orch', 'transcode management'],
|
||||
'Orchestrators/array_started' => ['array start', 'array started'],
|
||||
'Orchestrators/monthly_maintenance' => ['monthly orch', 'monthly maintenance'],
|
||||
'Orchestrators/sunday_morning_coffee_report' => ['coffee report', 'sunday report',
|
||||
'sunday morning coffee'],
|
||||
];
|
||||
foreach ($aliases as $id => $phrases) {
|
||||
foreach ($phrases as $p) if (str_contains($q, $p)) return $id;
|
||||
}
|
||||
|
||||
// Anything with a log but no alias — named outright, either as an id or a bare script name.
|
||||
$hits = [];
|
||||
foreach (vv_ai_log_ids() as $id) {
|
||||
$bare = basename($id);
|
||||
if (str_contains($q, strtolower($id)) || str_contains($q, strtolower($bare))) $hits[] = $id;
|
||||
}
|
||||
$hits = array_unique($hits);
|
||||
if (count($hits) === 1) return reset($hits);
|
||||
|
||||
// Longest match wins only when one candidate contains every other — "daily_sync_maintenance"
|
||||
// over "sync", not a coin toss between two unrelated scripts.
|
||||
if (count($hits) > 1) {
|
||||
usort($hits, fn($a, $b) => strlen($b) - strlen($a));
|
||||
$longest = $hits[0];
|
||||
foreach (array_slice($hits, 1) as $h) {
|
||||
if (!str_contains(strtolower($longest), strtolower(basename($h)))) return '';
|
||||
}
|
||||
return $longest;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Every script id that has a log, relative to LOG_DIR and without the extension. One level of
|
||||
// nesting, which is how the log tree is actually laid out (Orchestrators/, Plugin/).
|
||||
function vv_ai_log_ids(): array {
|
||||
$base = realpath(LOG_DIR);
|
||||
if ($base === false) return [];
|
||||
$ids = [];
|
||||
foreach (glob($base . '/*.log') ?: [] as $f) {
|
||||
$ids[] = basename($f, '.log');
|
||||
}
|
||||
foreach (glob($base . '/*/*.log') ?: [] as $f) {
|
||||
$ids[] = basename(dirname($f)) . '/' . basename($f, '.log');
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user