Let a finding be answered in words, matched rather than judged
The reply to "shall I fix it" ends in a conf write, so the three actions are matched by pattern with an ambiguity refusal instead of being inferred — and a phrase that supports two readings asks again rather than picking one.
This commit is contained in:
@@ -383,6 +383,106 @@ function vv_ai_findings_for_chat(int $limit = 3): array {
|
|||||||
return array_slice(vv_ai_findings_list(['needs_operator']), 0, max(1, $limit));
|
return array_slice(vv_ai_findings_list(['needs_operator']), 0, max(1, $limit));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Answering a finding in words ─────────────────────────────────────────────────────────────
|
||||||
|
// The buttons are unambiguous by construction. This is for the other path — replying "yeah go
|
||||||
|
// ahead" in the chat that raised the finding — and it is matched here rather than asked of the
|
||||||
|
// model, because the model's answer would be a conf write and a wrong reading of "no, leave it"
|
||||||
|
// is not recoverable by apologising.
|
||||||
|
//
|
||||||
|
// Same shape as vv_ai_route_from_chat(): anchored patterns, most specific first, and anything
|
||||||
|
// unrecognised returns null so the assistant asks again instead of guessing. Two actions both
|
||||||
|
// matching is also null — "leave it, I know" and "leave it for now" differ by one clause and
|
||||||
|
// mean different things, so a phrase that supports both is not an instruction yet.
|
||||||
|
const VV_AI_ACTION_PATTERNS = [
|
||||||
|
// Acknowledge — "this is deliberate, stop telling me".
|
||||||
|
'ack' => [
|
||||||
|
'/\b(i|we) know\b/u',
|
||||||
|
'/\b(that|this|it)(?:\'s| is) (fine|expected|intentional|deliberate|on purpose)\b/u',
|
||||||
|
'/\bon purpose\b/u',
|
||||||
|
'/\b(aware|acknowledge|ack)\b/u',
|
||||||
|
'/\bmeant to be\b/u',
|
||||||
|
],
|
||||||
|
// Apply the proposed value.
|
||||||
|
'fix' => [
|
||||||
|
'/\bfix (it|that|this|them)?\b/u',
|
||||||
|
'/\b(go ahead|do it|apply|make the change|change it|update it|correct it)\b/u',
|
||||||
|
// A bare affirmative, as the whole message — "yes" answering "shall I fix it" is an
|
||||||
|
// instruction, "yes it looks wrong" is agreement about the diagnosis and nothing more.
|
||||||
|
// A trailing please is still bare.
|
||||||
|
'/\b(yes|yeah|yep|yup|sure|ok|okay)\b(\s*,?\s*please)?[\s,.!]*$/u',
|
||||||
|
'/\bplease do\b/u',
|
||||||
|
],
|
||||||
|
// Not now — no state written, it comes back next sweep.
|
||||||
|
'cancel' => [
|
||||||
|
'/\b(not now|later|leave it (alone|for now)|skip( it)?|cancel|ignore for now)\b/u',
|
||||||
|
'/\b(no|nope|nah)\b[\s,.!]*$/u',
|
||||||
|
'/\b(don\'?t|do not) (fix|touch|change|write|apply)\b/u',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Returns 'fix' | 'ack' | 'cancel', or null when the reply does not clearly mean one of them.
|
||||||
|
//
|
||||||
|
// Only call this when a finding is actually pending. A bare "yes" means fix in answer to "shall
|
||||||
|
// I fix it" and means nothing at all on its own, and the difference is context this function
|
||||||
|
// cannot see.
|
||||||
|
function vv_ai_finding_action_from_text(string $text): ?string {
|
||||||
|
$t = strtolower(trim($text));
|
||||||
|
if ($t === '') return null;
|
||||||
|
$t = preg_replace('/\s+/', ' ', $t);
|
||||||
|
|
||||||
|
$matched = [];
|
||||||
|
foreach (VV_AI_ACTION_PATTERNS as $action => $patterns) {
|
||||||
|
foreach ($patterns as $re) {
|
||||||
|
if (preg_match($re, $t)) { $matched[$action] = true; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exactly one reading, or none. "leave it, I know" hits both ack and cancel; that is a
|
||||||
|
// sentence the operator should be asked to restate, not one to pick a winner from.
|
||||||
|
return count($matched) === 1 ? array_key_first($matched) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carry out an answered action against a stored finding.
|
||||||
|
//
|
||||||
|
// Fix goes through the same guarded write path as everything else, and is the one place a
|
||||||
|
// toggle may be written — because reaching here means the operator asked for it by name. The
|
||||||
|
// unattended sweep never calls this.
|
||||||
|
function vv_ai_finding_apply_action(string $id, string $action, string $note = ''): array {
|
||||||
|
$f = vv_ai_finding_get($id);
|
||||||
|
if ($f === null) return ['ok' => false, 'error' => 'no such finding'];
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
case 'ack':
|
||||||
|
return ['ok' => vv_ai_finding_ack($id, $note), 'action' => 'ack'];
|
||||||
|
|
||||||
|
case 'cancel':
|
||||||
|
// Deliberately writes nothing at all. "Not now" is not a state, it is the absence of
|
||||||
|
// one — recording it would make the finding look decided when it is still open.
|
||||||
|
return ['ok' => true, 'action' => 'cancel'];
|
||||||
|
|
||||||
|
case 'fix':
|
||||||
|
$proposed = $f['proposed'] ?? null;
|
||||||
|
if ($proposed === null) return ['ok' => false, 'error' => 'nothing proposed to write'];
|
||||||
|
|
||||||
|
$key = (string)$f['conf_key'];
|
||||||
|
$ok = vv_conf_write_changes([[
|
||||||
|
'file' => (string)($f['conf_file'] ?? 'master.conf'),
|
||||||
|
'key' => $key,
|
||||||
|
'value' => (string)$proposed,
|
||||||
|
'type' => 'scalar',
|
||||||
|
]]);
|
||||||
|
$wrote = !in_array(false, $ok, true);
|
||||||
|
|
||||||
|
if ($wrote) {
|
||||||
|
vv_ai_finding_set_state($id, 'fixed',
|
||||||
|
$note !== '' ? $note : 'Wrote ' . $key . ' at the operator\'s request.');
|
||||||
|
}
|
||||||
|
return ['ok' => $wrote, 'action' => 'fix',
|
||||||
|
'error' => $wrote ? null : 'conf write refused — see conf_changes.log'];
|
||||||
|
}
|
||||||
|
return ['ok' => false, 'error' => 'unknown action'];
|
||||||
|
}
|
||||||
|
|
||||||
// ── Resolving a log line back to a conf key ──────────────────────────────────────────────────
|
// ── Resolving a log line back to a conf key ──────────────────────────────────────────────────
|
||||||
// By value wherever possible, by name only as a fallback.
|
// By value wherever possible, by name only as a fallback.
|
||||||
//
|
//
|
||||||
|
|||||||
Reference in New Issue
Block a user