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',
|
. 'last night|duration|how long|rundown|summar\w*|recap|status|results?|outcome)\b/i',
|
||||||
$question);
|
$question);
|
||||||
|
|
||||||
// Permission first, need second: the capability decides whether live state may be attached at
|
// The target and its run record are resolved before the diagnostic decision rather than after,
|
||||||
// all, and only then does the phrasing decide whether this particular question warrants it.
|
// because whether the run failed is the single most useful input to that decision and it costs
|
||||||
// troubleshoot needs no phrasing test — the operator opened a log to get there.
|
// one small JSON read to know it.
|
||||||
$diagnostic = $can('health') && ($profile === 'troubleshoot' || $runOutcome || (bool)preg_match(
|
$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|'
|
'/\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',
|
. 'never runs?|didn.t|won.t|debug|troubleshoot|diagnos)/i',
|
||||||
$question
|
$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 = '';
|
$diagBlock = '';
|
||||||
if ($diagnostic) {
|
if ($diagnostic) {
|
||||||
@@ -301,19 +327,12 @@ if ($diagnostic) {
|
|||||||
// The troubleshooting profile gets the actual tail of the one log the operator is looking at,
|
// 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
|
// 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.
|
// 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
|
// $runTarget and $rec were resolved above, where the diagnostic decision needed them. The record
|
||||||
// question. The second half is what makes a run-outcome question work from any view: asking how
|
// is emitted before the tail because it states the outcome where the tail only implies it: a log
|
||||||
// the daily orchestrator went while looking at the suggestions list is the ordinary case, not an
|
// ending on a tidy summary block looks identical whether the script exited 0 or was killed on the
|
||||||
// edge one, and requiring them to open the log first is asking them to do the lookup themselves.
|
// next line, and the difference is the whole answer.
|
||||||
$runTarget = ($can('scoped_log') && $scope !== '') ? $scope : '';
|
|
||||||
if ($runTarget === '' && $runOutcome) $runTarget = $namedTarget;
|
|
||||||
|
|
||||||
$scopedLog = null;
|
$scopedLog = null;
|
||||||
if ($runTarget !== '' && vv_ai_scope_ok($runTarget)) {
|
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']) {
|
if ($rec['ok']) {
|
||||||
$attached['run_record'] = $rec['status'] . ', exit ' . var_export($rec['exit'], true);
|
$attached['run_record'] = $rec['status'] . ', exit ' . var_export($rec['exit'], true);
|
||||||
$diagBlock .= 'RUN RECORD for ' . $runTarget . " (authoritative — how the last run ended)\n"
|
$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.
|
# 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
|
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 ──────────────────────────
|
# ── 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
|
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
|
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.
|
# about, which is indistinguishable from a right answer unless you already knew.
|
||||||
how did sync go | varaverk | | | target=none run=no
|
how did sync go | varaverk | | | target=none run=no
|
||||||
what does RSYNC_ENABLED do | varaverk | | | target=none run=no has=conf_keys
|
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 ──────────
|
# ── 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
|
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-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;
|
.vv-activity-dur { color: #444; font-size: 10px; white-space: nowrap; flex-shrink: 0;
|
||||||
min-width: 32px; text-align: right; }
|
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 */
|
/* Log search */
|
||||||
#vv-log-search { width: 120px; font-size: 11px; padding: 2px 6px;
|
#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.
|
// 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 = [];
|
$hits = [];
|
||||||
foreach (vv_ai_log_ids() as $id) {
|
foreach (vv_ai_log_ids() as $id) {
|
||||||
$bare = basename($id);
|
foreach ([strtolower($id), strtolower(basename($id))] as $needle) {
|
||||||
if (str_contains($q, strtolower($id)) || str_contains($q, strtolower($bare))) $hits[] = $id;
|
if ($needle !== '' && preg_match('/\b' . preg_quote($needle, '/') . '\b/', $q)) {
|
||||||
|
$hits[] = $id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$hits = array_unique($hits);
|
$hits = array_unique($hits);
|
||||||
if (count($hits) === 1) return reset($hits);
|
if (count($hits) === 1) return reset($hits);
|
||||||
|
|||||||
@@ -2437,6 +2437,25 @@ function vvAiDockSend() {
|
|||||||
}).catch(e => vvAiDockDone('Request failed: ' + e, true));
|
}).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) {
|
function vvAiDockPoll(token, started) {
|
||||||
started = started || Date.now();
|
started = started || Date.now();
|
||||||
clearTimeout(vvAiPoll);
|
clearTimeout(vvAiPoll);
|
||||||
@@ -3034,16 +3053,29 @@ function vvLoadRecentActivity() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const now = Math.floor(Date.now() / 1000);
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
const withAi = vvAiDockOn();
|
||||||
let errors = 0;
|
let errors = 0;
|
||||||
let html = '<div class="vv-activity-list">';
|
let html = '<div class="vv-activity-list">';
|
||||||
for (const r of d.runs) {
|
for (const r of d.runs) {
|
||||||
const cls = r.status === 'ok' ? 'vv-stat-ok' : r.status === 'warn' ? 'vv-stat-warn' : 'vv-stat-error';
|
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) + ')">'
|
html += '<div class="vv-activity-row" onclick="vvOpenRight(' + JSON.stringify(r.id) + ')">'
|
||||||
+ '<span class="vv-activity-dot ' + cls + '">●</span>'
|
+ '<span class="vv-activity-dot ' + cls + '">●</span>'
|
||||||
+ '<span class="vv-activity-label">' + vvEscHtml(r.label) + '</span>'
|
+ '<span class="vv-activity-label">' + vvEscHtml(r.label) + '</span>'
|
||||||
+ '<span class="vv-activity-ago">' + vvTimeAgo(now - r.start) + '</span>'
|
+ '<span class="vv-activity-ago">' + vvTimeAgo(now - r.start) + '</span>'
|
||||||
+ '<span class="vv-activity-dur">' + vvFmtDur(r.dur) + '</span>'
|
+ '<span class="vv-activity-dur">' + vvFmtDur(r.dur) + '</span>'
|
||||||
|
+ ask
|
||||||
+ '</div>';
|
+ '</div>';
|
||||||
}
|
}
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|||||||
Reference in New Issue
Block a user