Ask about a run from the row it is on, and stop spending context on health checks when the run was clean
This commit is contained in:
@@ -265,14 +265,40 @@ $runOutcome = $namedTarget !== ''
|
||||
. 'last night|duration|how long|rundown|summar\w*|recap|status|results?|outcome)\b/i',
|
||||
$question);
|
||||
|
||||
// Permission first, need second: the capability decides whether live state may be attached at
|
||||
// all, and only then does the phrasing decide whether this particular question warrants it.
|
||||
// troubleshoot needs no phrasing test — the operator opened a log to get there.
|
||||
$diagnostic = $can('health') && ($profile === 'troubleshoot' || $runOutcome || (bool)preg_match(
|
||||
// The target and its run record are resolved before the diagnostic decision rather than after,
|
||||
// because whether the run failed is the single most useful input to that decision and it costs
|
||||
// one small JSON read to know it.
|
||||
$runTarget = ($can('scoped_log') && $scope !== '') ? $scope : '';
|
||||
if ($runTarget === '' && $runOutcome) $runTarget = $namedTarget;
|
||||
|
||||
$rec = ($runTarget !== '' && vv_ai_scope_ok($runTarget))
|
||||
? vv_ai_run_record($runTarget)
|
||||
: ['ok' => false];
|
||||
|
||||
// A run that ended, reported ok and exited zero. Anything else — a bad exit, a missing end time,
|
||||
// no record at all — is not clean and is treated as worth investigating.
|
||||
$runClean = $rec['ok'] && $rec['status'] === 'ok' && $rec['exit'] === 0 && $rec['end'] !== null;
|
||||
|
||||
$kwDiagnostic = (bool)preg_match(
|
||||
'/\b(why|fail(ed|ing|ure)?|error|broken?|not work|isn.t work|wrong|stuck|hang|'
|
||||
. 'never runs?|didn.t|won.t|debug|troubleshoot|diagnos)/i',
|
||||
$question
|
||||
));
|
||||
);
|
||||
|
||||
// Permission first, need second: the capability decides whether live state may be attached at
|
||||
// all, and only then does the phrasing decide whether this particular question warrants it.
|
||||
// troubleshoot needs no phrasing test — the operator opened a log to get there.
|
||||
//
|
||||
// A run-outcome question earns live state only when the run was not clean. "How did the daily
|
||||
// orch go" about a run that exited 0 was pulling in a fleet-wide health sweep and forty recent
|
||||
// warning lines to answer a question the run record and the log tail already answer completely —
|
||||
// roughly two thousand tokens taken from the passages to say nothing. When the run did fail, all
|
||||
// of that is exactly what explains why, so it stays.
|
||||
$diagnostic = $can('health') && (
|
||||
$profile === 'troubleshoot'
|
||||
|| $kwDiagnostic
|
||||
|| ($runOutcome && !$runClean)
|
||||
);
|
||||
|
||||
$diagBlock = '';
|
||||
if ($diagnostic) {
|
||||
@@ -301,19 +327,12 @@ if ($diagnostic) {
|
||||
// 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.
|
||||
// The target is whatever the operator has open, and failing that whatever they named in the
|
||||
// question. The second half is what makes a run-outcome question work from any view: asking how
|
||||
// the daily orchestrator went while looking at the suggestions list is the ordinary case, not an
|
||||
// edge one, and requiring them to open the log first is asking them to do the lookup themselves.
|
||||
$runTarget = ($can('scoped_log') && $scope !== '') ? $scope : '';
|
||||
if ($runTarget === '' && $runOutcome) $runTarget = $namedTarget;
|
||||
|
||||
// $runTarget and $rec were resolved above, where the diagnostic decision needed them. The record
|
||||
// is emitted before the tail because it states the outcome where the tail only implies it: a log
|
||||
// ending on a tidy summary block looks identical whether the script exited 0 or was killed on the
|
||||
// next line, and the difference is the whole answer.
|
||||
$scopedLog = null;
|
||||
if ($runTarget !== '' && vv_ai_scope_ok($runTarget)) {
|
||||
// The record first: it states the outcome, where the tail only implies it. A log that ends
|
||||
// on a tidy summary block looks identical whether the script exited 0 or was killed on the
|
||||
// next line, and the difference is the whole answer.
|
||||
$rec = vv_ai_run_record($runTarget);
|
||||
if ($rec['ok']) {
|
||||
$attached['run_record'] = $rec['status'] . ', exit ' . var_export($rec['exit'], true);
|
||||
$diagBlock .= 'RUN RECORD for ' . $runTarget . " (authoritative — how the last run ended)\n"
|
||||
|
||||
@@ -41,6 +41,14 @@ how long did the daily orch take | varaverk | | | run=yes has=run_record
|
||||
# Works from the AI tab, which sends no scope at all — resolution is from the question, not the page.
|
||||
how did the daily orch go last run | varaverk | | | target=Orchestrators/daily_sync_maintenance has=run_record
|
||||
|
||||
# ── A clean run earns the record and the log, and nothing else ─────────────────────────────────
|
||||
# The health sweep and the recent-warning lines are ~660 tokens that answer nothing when the run
|
||||
# exited 0, taken straight out of the passages' budget. They come back the moment it did not.
|
||||
lets check the daily orch logs and see how it went | varaverk | Scheduler | | diag=no hasnt=health,warnings has=run_record,log_tail
|
||||
how did the daily orch go | varaverk | | | diag=no hasnt=health,warnings
|
||||
why did the daily orch fail | varaverk | | | diag=yes has=health,warnings,log_tail
|
||||
why did the weekly orch fail | varaverk | | | diag=yes has=health,warnings
|
||||
|
||||
# ── Definitional questions must NOT be answered with last night's log ──────────────────────────
|
||||
what does the daily orchestrator do | varaverk | | | target=Orchestrators/daily_sync_maintenance run=no hasnt=run_record,log_tail
|
||||
how does the daily orch work | varaverk | | | run=no hasnt=run_record,log_tail
|
||||
@@ -51,6 +59,15 @@ what is the daily orchestrator | varaverk | | | run=no hasnt=run_record
|
||||
# about, which is indistinguishable from a right answer unless you already knew.
|
||||
how did sync go | varaverk | | | target=none run=no
|
||||
what does RSYNC_ENABLED do | varaverk | | | target=none run=no has=conf_keys
|
||||
# Whole words only. "fail" contains "ai", and ai.log is a real log id — a substring match here
|
||||
# attached the AI's own log to questions about everything else.
|
||||
why did this run fail | varaverk | | | target=none
|
||||
is the mail relay working again | varaverk | | | target=none
|
||||
|
||||
# ── The Recent Activity buttons: one click from a row to an answer about that run ──────────────
|
||||
# Both open the log first, so the dock is scoped to troubleshoot against that script's log id.
|
||||
Why did this run fail? | troubleshoot | Orchestrators/daily_sync_maintenance | | diag=yes has=log_tail,run_record
|
||||
How did this run go? | troubleshoot | Orchestrators/weekly_sync_maintenance | | has=log_tail,run_record
|
||||
|
||||
# ── General Chat holds nothing, and hands Varaverk questions up rather than deferring ──────────
|
||||
how was your day | chat | | | profile=chat caps=none hasnt=health,log_tail,incidents,conf_keys
|
||||
|
||||
@@ -635,6 +635,15 @@ body.vv-fullscreen #displaybox { padding-left: 1rem !important; padding-top: .5r
|
||||
.vv-activity-ago { color: #555; font-size: 10px; white-space: nowrap; flex-shrink: 0; }
|
||||
.vv-activity-dur { color: #444; font-size: 10px; white-space: nowrap; flex-shrink: 0;
|
||||
min-width: 32px; text-align: right; }
|
||||
/* Hands a run straight to the assistant. Hidden until the row is hovered — every row carries one
|
||||
and shown always they read as the point of the list, which is the run history, not the AI. */
|
||||
.vv-activity-ask { flex-shrink: 0; visibility: hidden; font: inherit; font-size: 10px;
|
||||
color: #5a7a8a; background: #12191d; border: 1px solid #24343d;
|
||||
border-radius: 3px; padding: 1px 6px; cursor: pointer; }
|
||||
.vv-activity-row:hover .vv-activity-ask { visibility: visible; }
|
||||
.vv-activity-ask:hover { color: #8fb0c4; border-color: #2d4a6a; }
|
||||
.vv-activity-ask.vv-ask-bad { color: #c88; border-color: #4a2a2a; background: #1d1212; }
|
||||
.vv-activity-row:focus-within .vv-activity-ask { visibility: visible; }
|
||||
|
||||
/* Log search */
|
||||
#vv-log-search { width: 120px; font-size: 11px; padding: 2px 6px;
|
||||
|
||||
@@ -855,10 +855,19 @@ function vv_ai_resolve_run_target(string $question): string {
|
||||
}
|
||||
|
||||
// Anything with a log but no alias — named outright, either as an id or a bare script name.
|
||||
//
|
||||
// Whole words only. A substring test matched the `ai` log inside the word "f-ai-l", so
|
||||
// "why did this run fail" resolved to ai.log and would have attached it to a question about
|
||||
// something else entirely. Short ids make that failure common rather than exotic: mail, again,
|
||||
// available, maintenance all contain it.
|
||||
$hits = [];
|
||||
foreach (vv_ai_log_ids() as $id) {
|
||||
$bare = basename($id);
|
||||
if (str_contains($q, strtolower($id)) || str_contains($q, strtolower($bare))) $hits[] = $id;
|
||||
foreach ([strtolower($id), strtolower(basename($id))] as $needle) {
|
||||
if ($needle !== '' && preg_match('/\b' . preg_quote($needle, '/') . '\b/', $q)) {
|
||||
$hits[] = $id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$hits = array_unique($hits);
|
||||
if (count($hits) === 1) return reset($hits);
|
||||
|
||||
@@ -2437,6 +2437,25 @@ function vvAiDockSend() {
|
||||
}).catch(e => vvAiDockDone('Request failed: ' + e, true));
|
||||
}
|
||||
|
||||
// Asks about one run from the Recent Activity list, in a single click.
|
||||
//
|
||||
// It opens the log first rather than asking from wherever the operator happens to be standing.
|
||||
// That is not decoration: vvOpenRight() runs vvShowLogMode(), which scopes the dock to
|
||||
// troubleshoot against this script's log id — the profile that gets the log tail and may file a
|
||||
// bug. Asking without it would send the question up under the assistant's contract with no log
|
||||
// attached, which is the failure this whole day was spent removing.
|
||||
//
|
||||
// The question is posted as text the operator can see in the transcript, not hidden in the
|
||||
// request, so what was asked on their behalf is never a mystery.
|
||||
function vvAiAskRun(id, failed, ev) {
|
||||
if (ev) ev.stopPropagation();
|
||||
if (!vvAiDockOn() || vvAiBusy) return;
|
||||
vvOpenRight(id);
|
||||
const input = document.getElementById('vv-ai-dock-input');
|
||||
input.value = failed ? 'Why did this run fail?' : 'How did this run go?';
|
||||
vvAiDockSend();
|
||||
}
|
||||
|
||||
function vvAiDockPoll(token, started) {
|
||||
started = started || Date.now();
|
||||
clearTimeout(vvAiPoll);
|
||||
@@ -3034,16 +3053,29 @@ function vvLoadRecentActivity() {
|
||||
return;
|
||||
}
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const withAi = vvAiDockOn();
|
||||
let errors = 0;
|
||||
let html = '<div class="vv-activity-list">';
|
||||
for (const r of d.runs) {
|
||||
const cls = r.status === 'ok' ? 'vv-stat-ok' : r.status === 'warn' ? 'vv-stat-warn' : 'vv-stat-error';
|
||||
if (r.status !== 'ok' && r.status !== 'skipped') errors++;
|
||||
const bad = r.status !== 'ok' && r.status !== 'skipped';
|
||||
if (bad) errors++;
|
||||
// The question is on the row because that is where the operator already is when they want
|
||||
// it. Reaching the same answer otherwise means noticing the red dot, clicking through to
|
||||
// the log, finding the dock, and typing out what the row already knows.
|
||||
const ask = withAi
|
||||
? '<button class="vv-activity-ask' + (bad ? ' vv-ask-bad' : '') + '"'
|
||||
+ ' title="' + (bad ? 'Ask the assistant why this run failed'
|
||||
: 'Ask the assistant how this run went') + '"'
|
||||
+ ' onclick="vvAiAskRun(' + JSON.stringify(r.id) + ',' + bad + ',event)">'
|
||||
+ (bad ? 'why?' : 'recap') + '</button>'
|
||||
: '';
|
||||
html += '<div class="vv-activity-row" onclick="vvOpenRight(' + JSON.stringify(r.id) + ')">'
|
||||
+ '<span class="vv-activity-dot ' + cls + '">●</span>'
|
||||
+ '<span class="vv-activity-label">' + vvEscHtml(r.label) + '</span>'
|
||||
+ '<span class="vv-activity-ago">' + vvTimeAgo(now - r.start) + '</span>'
|
||||
+ '<span class="vv-activity-dur">' + vvFmtDur(r.dur) + '</span>'
|
||||
+ ask
|
||||
+ '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
Reference in New Issue
Block a user