Put the findings where they can be answered

Seven findings had been sitting in needs_operator with no way to reach the operator: the only
way to read one was the sweep tool over SSH. The card states the gate as well as the list,
because an empty list means nothing found when repair is on and nothing is looking when it is
off. Buttons come from the actions the endpoint returned for that row, so a tab left open
overnight cannot offer a choice the store has moved past.
This commit is contained in:
Gmer4Lfe
2026-08-09 21:14:44 -04:00
parent b20a46ba81
commit 77fb1abb85
2 changed files with 257 additions and 7 deletions
+58
View File
@@ -65,6 +65,8 @@
// GET ?action=memory_get the operator memory file and its budget
// GET ?action=chats stored conversations, newest first, metadata only
// GET ?action=chat_get&id=<hex32> one stored conversation with its transcript
// GET ?action=findings [all=1] repair findings, open only unless all=1
// POST action=finding_action id=<hex12> act=fix|ack|dismiss|reopen|cancel [note=…]
// POST action=ask question=… [history=<JSON>] [kind=…] [think=0|1]
// POST action=memory_set memory=… replace the memory file
// POST action=clear token=<hex32> discard a finished job
@@ -77,11 +79,14 @@
// poll {"ok":true,"job":{"status":"retrieving|generating|done|error",…}}
// clear {"ok":true}
// chats {"ok":true,"chats":[{id,ts,profile,title,turns}],"max":N}
// findings {"ok":true,"repair":{enabled,autofix,last},"findings":[…],"counts":{…}}
// finding_action {"ok":true,"action":"fix"}
// chat_save {"ok":true,"id":"<hex32>","title":…}
// {"ok":false,"error":…}
//
// DEPENDS ON
// include/ai.php vv_ai_stats(), vv_ai_config(), vv_ai_job_*()
// include/ai_repair.php the findings store — loaded only by the two actions that read it
// Tools/ai_chat_worker.php the detached worker
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// First executable statement, deliberately dependency-free. A request that is rejected by the
@@ -290,6 +295,59 @@ if ($action === 'bug_close') {
exit;
}
// ── findings / finding_action ─────────────────────────────────────────────────
// What the repair sweep found, and the operator's answer to it. include/ai_repair.php is pulled
// in here rather than at the top of the file: it is the largest include in the plugin and poll
// runs once a second per open tab, so it is loaded by the two actions that need it and by nothing
// else.
//
// Neither action is gated on AI_REPAIR_ENABLED. Findings filed while it was on do not stop being
// true when it goes off, and answering them — including saying "this was never a problem" — is
// exactly what an operator turning the feature off is likely to want to do first. The gate states
// are reported instead, so the card can say what is running rather than the endpoint pretending
// the store is empty.
if ($action === 'findings' || $action === 'finding_action') {
require_once dirname(__DIR__) . '/include/ai_repair.php';
if ($action === 'findings') {
// Closed findings are the history — what was dismissed, what a fix actually fixed — and
// they are asked for explicitly rather than shipped with every poll of the open list.
$rows = [];
$open = 0; $needs = 0;
foreach (vv_ai_findings_list(($_GET['all'] ?? '') === '1' ? [] : ['open', 'needs_operator']) as $f) {
$state = (string)($f['state'] ?? 'open');
if ($state === 'open') $open++;
elseif ($state === 'needs_operator') $needs++;
// The three things the page must not decide for itself: which actions this row
// offers, and what its state and kind mean in words.
$f['actions'] = vv_ai_finding_actions($f);
$f['state_label'] = VV_AI_FINDING_STATES[$state] ?? '';
$f['kind_label'] = VV_AI_FINDING_KINDS[(string)($f['kind'] ?? '')] ?? '';
$rows[] = $f;
}
echo json_encode(['ok' => true,
'repair' => ['enabled' => vv_ai_repair_enabled(),
'autofix' => vv_ai_repair_autofix_enabled(),
'last' => vv_ai_sweep_last()],
'findings' => $rows,
'counts' => ['open' => $open, 'needs_operator' => $needs, 'shown' => count($rows)]]);
exit;
}
// POST, because fix writes conf through the guarded path and every other answer writes state.
// Which actions are legal for a given row is vv_ai_finding_apply_action()'s call, not this
// endpoint's — a tab left open overnight is holding buttons the store has moved past.
if (!$isPost) { http_response_code(405); echo json_encode(['ok' => false, 'error' => 'POST only']); exit; }
$fid = trim($_POST['id'] ?? '');
$act = trim($_POST['act'] ?? '');
$r = vv_ai_finding_apply_action($fid, $act, trim($_POST['note'] ?? ''));
vv_ai_log(sprintf('finding_action id=%s act=%s %s', $fid, $act,
$r['ok'] ? 'ok' : 'FAILED: ' . ($r['error'] ?? '?')));
echo json_encode($r);
exit;
}
// ── incident_add ──────────────────────────────────────────────────────────────
// Appends one operator-written "this was the fix" note against a scope. POST only, and the
// scope is whitelisted the same way ask's is — it is written to a file that later rides in a