Fuel Watch
Показывай только АЗС со свежим положительным или вероятно положительным свидетельством в основном списке. Все варианты одного октанового числа считай одной пользовательской маркой: базовый, премиальный и брендовый 95-й выводятся только как АИ-95, без отдельных строк 95+, «Экто», G-Drive и подобных. Исходную метку варианта сохраняй внутри только для provenance и консервативной проверки отрицательных данных. Отказ источника, пустая выборка и отсутствие бензина — разные результаты. Всегда называй здоровье каждого источника, свежесть и уверенность, а также предупреждай, что страничные/краудсорсинговые данные могут запаздывать. Каждый отчёт также содержит до трёх прогнозов ближайшего появления по накопленной истории либо явное сообщение, что статистики пока недостаточно.
Неприкосновенный формат отчёта
Вывод report.mjs — единственный канонический пользовательский отчёт. Публикуй его содержимое как обычный Markdown без каких-либо изменений: не пересказывай своими словами, не сокращай, не переставляй строки, не объединяй с собственным списком и не удаляй Markdown-разметку. В частности, каждая сгенерированная ссылка на Яндекс Карты должна дойти до пользователя неизменной.
Не заключай отчёт в code fence или blockquote: ссылки должны остаться кликабельными. Допустима только отдельная короткая служебная фраза до или после полного отчёта, если нужно объяснить ошибку запуска; она не заменяет и не модифицирует сам отчёт. Если отчёт получен как JSON, декодируй поле markdown и опубликуй именно его значение. Перед отправкой проверь, что ни одна строка или ссылка из результата report.mjs не потеряна.
Подготовка
Определи корень skill по расположению этого SKILL.md и используй его абсолютный путь как FUEL_WATCH_ROOT. Каталог skill — заменяемый read-only артефакт: никогда не создавай и не меняй в нём config, state, историю, node_modules или другие runtime-файлы. Пользовательские настройки и накопленные данные автоматически живут в ~/.fuel-watch/: config/, history/, state/ и monitors/. Переменная FUEL_WATCH_HOME может перенести весь этот каталог. Не меняй пользовательскую конфигурацию без явного запроса.
Сначала проверь внешний runtime командами command -v agent-browser и agent-browser --version в том же окружении, где будет выполняться collection. Если команда доступна, используй установленную версию без переустановки. Никогда самостоятельно не выполняй npm install -g agent-browser, npm update -g agent-browser, npx agent-browser или agent-browser install. Если команда отсутствует или не запускается, ничего глобально не устанавливай: верни BROWSER_UNAVAILABLE, покажи результат проверки и запроси у пользователя отдельное разрешение на изменение окружения.
Runtime поставляется заранее собранным в dist/ вместе со всеми Node.js-зависимостями. На целевой машине никогда не выполняй npm install, npm ci, npm update или npx для Fuel Watch. Отсутствие node_modules в установленном skill штатно. Если dist/scripts/collect.mjs отсутствует, верни ошибку повреждённого/неполного skill-пакета; не пытайся собирать его на целевой машине.
В штатной collection сайты открываются только через agent-browser, которым управляют скрипты. Не добавляй curl, fetch, прямой HTTP-клиент, CAPTCHA solver, dashboard, HAR, video, trace, restore/profile или фоновый браузер.
Штатная collection всегда использует HEADED Chromium без stealth-флагов и подмены fingerprint. Если доступен пользовательский display, BrowserRunner сохраняет DISPLAY/WAYLAND_DISPLAY/XAUTHORITY; иначе agent-browser может использовать собственный Xvfb, но браузер всё равно остаётся non-headless. Не переноси результат HEADLESS-диагностики на обычный браузер или доступность сайта вообще. Каждый источник получает отдельную короткоживущую session, источники обрабатываются строго последовательно, поэтому одновременно активно не более одного окна.
BrowserRunner сначала обязательно устанавливает --allowed-domains. Только если agent-browser 0.35.1 возвращает известную CDP-ошибку установки network controls, он один раз пересоздаёт owned session без controls и сохраняет fail-closed проверки точного final host и последующего page drift. Такой результат всегда получает предупреждение BROWSER_NETWORK_CONTROLS_DEGRADED; не скрывай его и не называй fallback эквивалентом полноценной сетевой изоляции.
Штатный workflow не управляет вкладками и не собирает визуальные артефакты: он вызывает только скрипты ниже. Если пользователь явно просит показать браузер, исследовать DOM/network/console или отладить источник, сначала прочитай references/browser-debugging.md и выполняй диагностику отдельно от collection-run.
Разовая проверка
Создай временный каталог через
mktemp -d.Выполни:
node "$FUEL_WATCH_ROOT/dist/scripts/collect.mjs" --output "$RUN_DIR/snapshot.json" node "$FUEL_WATCH_ROOT/dist/scripts/report.mjs" --snapshot "$RUN_DIR/snapshot.json"Опубликуй stdout
report.mjsцеликом и неизменным обычным Markdown даже при коде 2: он объясняет деградацию и не означает «бензина нет». Не создавай вместо него собственную сводку.При коде 75 предупреди о
CLEANUP_FAILED, затем один раз проверь очистку только записанного owned namespace. Не запускай глобальныйagent-browser close --allбез namespace.Удали временный каталог. Компактная история статусов и часовых rolling-count сигналов по октановым маркам 92/95/98/100 сохраняется отдельно в пользовательском state-каталоге, автоматически обрезается до последних 7 дней и используется следующими разовыми проверками и monitoring tick’ами. Перед сравнением соседних tick все варианты одного октанового числа агрегируются внутри
station + source + tick: rolling-count суммируется; статус имеет приоритетIN_STOCK → LIMITED → OUT_OF_STOCK → UNKNOWN, поэтому нечитаемый вариант игнорируется при наличии хотя бы одного читаемого. Переход октана засчитывается только при свидетеле — один и тот же вариант присутствует в обоих tick и действительно изменилсяOUT_OF_STOCK → IN_STOCK/LIMITEDилиcount 0 → count > 0; появление/исчезновение строки само по себе событием не является. Дизель, газ, неизвестные продукты и прочие октановые числа исключаются. Путь можно явно задать через--history; переменнаяFUEL_WATCH_HISTORY_PATHменяет default path.
Мониторинг в текущей задаче
Не создавай automation, daemon или новую задачу. Мониторинг выполняет активный агент в этом диалоге до команды пользователя остановиться или внешнего прерывания.
Инициализируй монитор и сохрани напечатанные
monitorIdи абсолютныйstateDirв контексте задачи:node "$FUEL_WATCH_ROOT/dist/scripts/monitor.mjs" initПеред первым и каждым следующим tick проверь
monitor.mjs due --state-dir "$STATE_DIR". Когда tick наступил:- запусти
collect.mjs --previous "$STATE_DIR/previous.json"только если такой snapshot подготовлен агентом из предыдущего committed state; - создай отчёт
report.mjs --snapshot "$SNAPSHOT" --state-dir "$STATE_DIR" --json; - вызови
monitor.mjs prepare --state-dir "$STATE_DIR" --report-id "$REPORT_ID" --snapshot "$SNAPSHOT"; - опубликуй значение
markdownиз JSON целиком, неизменным и не в code fence; не пересказывай его и не удаляй ссылки; - только после успешной публикации вызови
monitor.mjs commit --state-dir "$STATE_DIR" --report-id "$REPORT_ID".
- запусти
Между tick’ами браузер уже закрыт. Жди foreground-командой
sleep 45или короче. После каждого фрагмента проверяй новый пользовательский ввод,monitor.mjs dueиSTOPчерез его полеstopped; не запускай единый 15-минутный sleep.После четырёх пустых tick’ов используй
report.mjs --compact, но продолжай cadence.При возобновлении задачи сначала вызови
monitor.mjs recover --state-dir "$STATE_DIR". Если есть pending report, повторно отрендери его с--recovered, сохрани тот же report ID и затем commit.При остановке вызови
node "$FUEL_WATCH_ROOT/dist/scripts/monitor.mjs" stop --state-dir "$STATE_DIR", выполни одну namespace-scoped очистку последнего browser-run и завершиnode "$FUEL_WATCH_ROOT/dist/scripts/monitor.mjs" cleanup --state-dir "$STATE_DIR". Подтверди удаление monitor-state; общие настройки, последний snapshot и 7-дневная история сохраняются.
Никогда не держи браузер во время ожидания. Любая коллекция чаще настроенного monitoring.intervalMinutes запрещена в monitoring mode.
Изменение зоны
Для проверки anchor-координат выполни read-only команду:
node "$FUEL_WATCH_ROOT/dist/scripts/resolve-area.mjs"
Покажи найденные координаты и drift пользователю. Перезапись разрешена только после явного подтверждения: повтори с --write --confirm. Три неуникальные или коллинеарные точки, неверные координаты и неразрешённый anchor приводят к fail-closed.
Интерпретация
низкая,средняяивысокаяв отчёте всегда означают уверенность Fuel Watch в собственной оценке, а не заявленную источником вероятность. Она выводится из свежести и специфичности свидетельства, provenance-групп и grade-specific активности. Не приписывай её отдельному сервису. Непроверенное source confidence/rating можно сохранить только как provenance; не подменяй им итоговую уверенность без формализованной семантики и калибровки.последний подтверждающий сигнал— возраст самого свежего observation, реально участвующего в текущем положительном вердикте.только чтоозначает возраст меньше одной минуты, а не обязательно изменение статуса или прибытие бензовоза. Источники текущей оценки и источники исторического перехода выводятся отдельными строками; не объединяй их в одну подпись.ЕСТЬ: свежий прямой сигнал любого варианта АИ-95.СКОРЕЕ ЕСТЬ: семейный/косвенный положительный сигнал или подтверждённое grade-specific возобновление активности. В пользовательском отчёте и истории варианты одного октанового числа не разделяй.- Не называй generic signal транзакцией.
TRANSACTIONS_RESUMED— только grade-specific разрыв не менее 60 минут и минимум два новых события за 20 минут. - Не трактуй время обновления как время поставки. «Появился между…» допустимо только для подтверждённого перехода из
NOT_AVAILABLE; иначе говори «впервые увидели» или «время появления неизвестно». - Прогноз — не факт поставки. Сильнейший допустимый обучающий сигнал — типичное местное время перехода настоящего grade-specific rolling-count
0 → ≥2по бензиновым маркам 92/95/98/100 после часового тихого окна. Не размножай агрегатный счётчик АЗС по маркам: живой Yandex сейчас отдаёт именно агрегатныйsignalsCountPerHour, поэтому он исключён из прогноза как потенциально содержащий дизельные сигналы. На текущих данных основной доступный признак общего подвоза — одновременный переход нескольких бензиновых статусов изOUT_OF_STOCKвIN_STOCK/LIMITED; одиночный переход слабее. Дизель, газ и неизвестные продукты не учитываются. Подтверждённые переходы union АИ-95 служат последним fallback. Сначала используй историю этой АЗС, затем бренда, затем зоны; всегда показывай интервал, тип сигнала, число эпизодов и уверенность. Не придумывай время в холодном старте и не скрывай нехватку данных. - Разовые проверки тоже накапливают эту историю. Мониторинг не обязателен, но частые tick’и дают существенно лучшую временную разрешающую способность; редкие ручные запуски могут полностью пропустить короткий переход rolling-count.
- Presence-only очередь не сравнивается как короткая. Неизвестная очередь сортируется после известных.
- Yandex, gdebenz и benzonavt относятся к одной provenance group по умолчанию и сами по себе не дают независимого многоисточникового повышения уверенности.
- У Yandex station-level
lastSignalTimestampиsignalsCountPerHourне относятся к конкретной марке и не должны давать ей свежесть или повышать уверенность. Grade-наблюдение использует только timestamp/count из самой строки марки; агрегатное время допустимо для очереди и контекста АЗС. Если живой payload не содержит grade-time, источник честно возвращаетPARTIAL/NO_GRADE_FRESHNESS_METADATA, а неCOMPLETENESS_INVARIANTи не свежий вердикт. - У Benzonavt
fuels_nowтрактуется как исчерпывающий текущий список: непустой список без 95 — family-negative. Любойst.conflictдо формализации его семантики переводит топливное наблюдение вUNCERTAINи запрещает как рекомендацию, так и family-wide отрицание.queue.size=20_50/gt50отображай какLONG/VERY_LONG, используя собственноеqueue.at. - Каталожный список топлива 2GIS не доказывает текущее наличие; учитывай только явный current-status с
last_report_at. CAPTCHA означаетCHALLENGE; не пытайся её решать. - Семидневная история связывает физическую АЗС по сохранённым member IDs, а не только по изменчивому merged
stationKey, и сериализует параллельные read–modify–write через bounded lock.HISTORY_LOCK_TIMEOUTозначает недоступный прогноз, но не отменяет текущий отчёт.
Пример
Запрос: «Где сейчас есть 95-й, и где очередь меньше?»
Ожидаемый результат: один отчёт с timestamp и зоной, здоровьем Yandex/gdebenz/2GIS/Benzonavt, изменениями при наличии предыдущего tick, ранжированными только положительными АЗС, единственной агрегированной строкой АИ-95 со свежестью и уверенностью для каждой станции, отдельными счётчиками отрицательных/неизвестных данных, прогнозом до трёх ближайших появлений и обязательным предупреждением.
Ошибки
CHALLENGE,HTTP_ERROR,TIMEOUT,RESOURCE_BLOCKED,SCHEMA_CHANGED: назови источник и продолжи с остальными.- Все источники деградировали: сообщи «нет доступных свежих данных», не «бензина нет».
CLEANUP_FAILED/ код 75: данные можно показать, но предупреждение обязательно; выполни одну повторную namespace-scoped проверку очистки до ожидания или новой коллекции.- Невалидная конфигурация / код 2 до старта браузера: покажи точный JSON path из ошибки и ничего не собирай.
HISTORY_UNAVAILABLE: текущие данные можно показать, но прогноз недоступен; назови ошибку записи/чтения и не подменяй её выдуманным временем.- Временный state не удалился: сообщи абсолютный каталог и не утверждай, что monitoring полностью остановлен.
Проверка разработки
После изменения skill выполни npm test. Live smoke tests отделены и запускаются только по явному запросу: npm run test:live. Они никогда не входят в 15-минутный monitoring loop.