Files
brain/tools/standby-mode-control.mjs
T
Дмитрий abf2060328 feat standby: штатный режим - флаг, управляющий хук, сброс, страж в 12 хуков
Сессионный флаг standby-mode + управляющий UserPromptSubmit-хук рукопожатия + SessionStart-сброс. Страж if standbyActive в 12 блокирующих хуках; рельсы floor/snapshot/verify-gate не тронуты.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 10:07:04 +03:00

56 lines
3.2 KiB
JavaScript

#!/usr/bin/env node
/**
* standby-mode-control — UserPromptSubmit-хук штатного режима.
* Двухтактное рукопожатие включения, выключение одной фразой, баннер пока активно.
* Флаг пишется ТОЛЬКО здесь (реакция на промпт владельца). Fail-quiet: ошибка не ломает промпт.
*/
import { fileURLToPath } from 'url';
import { readStdin, parseEventJson, writeSentinel, readSentinel, removeSentinel, standbyActive } from './enforce-hook-helpers.mjs';
const ENABLE_RE = /штатн\p{L}*\s+режим/iu;
const CONFIRM_RE = /(?:^|\s)да,?\s*штатн/iu;
const DISABLE_RE = /выключ\p{L}*\s+штатн/iu;
const BANNER = '⚠️ ШТАТНЫЙ РЕЖИМ — стен нет (оставлены: пол, точки возврата, проверка-перед-пушем). Верни стену фразой «выключи штатный».';
const CONFIRM_ASK = '⚠️ Включить ШТАТНЫЙ — стен нет (останутся пол + проверка пуша). Точно? Подтверди фразой: «да, штатный».';
const ENABLED_MSG = '✅ ШТАТНЫЙ ВКЛЮЧЁН. ' + BANNER;
const DISABLED_MSG = '🔒 Штатный выключен — стена возвращена (предреализация).';
/** Чистое решение. Порядок проверок фиксирован: disable → confirm → enable-trigger → banner. */
export function decideControl({ text, active, pending }) {
if (DISABLE_RE.test(text)) return { action: 'disable', context: DISABLED_MSG };
if (CONFIRM_RE.test(text) && pending) return { action: 'enable', context: ENABLED_MSG };
if (ENABLE_RE.test(text) && !active && !pending) return { action: 'pending', context: CONFIRM_ASK };
if (active) return { action: 'banner', context: BANNER };
return { action: 'none', context: null };
}
function output(context) {
if (context) {
process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: context } }));
} else {
process.stdout.write('{}');
}
process.exit(0);
}
async function main() {
try {
const event = parseEventJson(await readStdin());
const sess = event.session_id || 'unknown';
const text = String(event.prompt || event.user_prompt || '');
const active = standbyActive(sess);
const pending = !!readSentinel('standby-pending', sess);
const r = decideControl({ text, active, pending });
if (r.action === 'disable') { removeSentinel('standby-mode', sess); removeSentinel('standby-pending', sess); }
else if (r.action === 'enable') { writeSentinel('standby-mode', sess, { active: true }); removeSentinel('standby-pending', sess); }
else if (r.action === 'pending') { writeSentinel('standby-pending', sess, { pending: true }); }
output(r.context);
} catch {
process.stdout.write('{}'); process.exit(0); // fail-quiet: ошибка не ломает промпт, режим не меняется
}
}
const isCli = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
if (isCli) main();