- Agent REST API (agent_api.uc): Added GET /openapi.json returning OpenAPI 3.0.3 spec for ChatGPT Custom GPTs, N8N, Dify, and LLM Agents
- AI Doctor (runtime.uc): Added compress_log_snippet() for deduplicating repeated log entries and ai_doctor_lang support (ru/en prompt selection)
- Telegram Bot (telegram.uc): Added /ai_doctor and /fix commands with Inline Keyboards for instant quick-fix execution
- Settings (renderAdvancedSettings.ts, settings.js): Added AI Doctor Language selector (ru/en)
- Documentation: Updated README.md and README.en.md with 2026 AI features
Backend (runtime.uc, agent_api.uc, tachyon CLI):
- Enrich AI Doctor LLM context with Watchdog state (OOM, streaks, active repairs) and last 15 critical log lines
- Support multi-fix parsing (quick_fixes array) and comma-separated FIX tags
- Add persistent history saving to /tmp/ai_doctor_last.json and ai_doctor_last endpoint
- Expand quick fix codes from 9 to 13 (added clear_dns_cache, update_subscriptions, reset_firewall, restart_network)
- Implement apply_quick_fix CLI command and REST API endpoint
Frontend (fe-app-tachyon, LuCI main.js):
- Add AI Doctor action button in Diagnostics tab
- Add shell methods aiDoctor, aiDoctorLast, applyQuickFix
- Render AI Doctor modal with Multi-Fix buttons and 'Fix All' option
- Integrate toast notifications and state updates
Backend (runtime.uc):
- query_llm() accepts model_override (5th param), falls back to provider default
- ai_doctor() reads ai_doctor_model from UCI and passes to query_llm
Frontend (renderAdvancedSettings.ts):
- aiDoctorModel state field (type / init / load / save)
- Model dropdown per provider with sensible defaults:
openai: gpt-4o-mini(default)/gpt-4o/gpt-4.1-mini/gpt-4.1/o4-mini
anthropic: claude-3-5-haiku(default)/claude-3-5-sonnet/claude-3-7-sonnet
deepseek: deepseek-chat(default)/deepseek-reasoner
custom: free-text input
- Switching provider resets model to '' (use new provider default)
Legacy LuCI (settings.js):
- ai_doctor_model Value field with placeholder '(provider default)'
With set -eo pipefail, when grep -qx exits early after finding a match
printf gets SIGPIPE and returns non-zero, which with pipefail triggers the
|| fail branch even though grep returned 0 (found).
Here-string avoids the pipe entirely.
- watchdog: удалён дублирующий check_tachyon_cli_running(), везде используется controller.check_tachyon_cli_running()
- watchdog: smart_detect_process_pending использует controller.proxy_port() вместо повторного парсинга config.json
- watchdog: удалена однострочная обёртка is_dns_dead() — заменена is_dns_working() напрямую
- watchdog: heal_proxy_connectivity требует streak >= 2 перед очисткой cache.db — устраняет ложные ночные уведомления при кратких просадках прокси-ноды
- event_controller: push_history переведён на slice() вместо ручного цикла
- event_controller: direct_ok использует тот же check_url что и proxy check — корректность при кастомном ai_proxy_health_url
- event_controller: добавлен subscription_fail_streak — одиночный таймаут сети больше не триггерит Telegram
- telegram: tg_request больше не создаёт временные файлы в /tmp — JSON передаётся напрямую через curl -d, устранена утечка tg_payload_*.json при kill процесса
- telegram: worker() при старте удаляет оставшиеся tg_payload_*.json от предыдущих сессий
- tests: исправлен шаблон mktemp в background_spawn_fds.sh — busybox не поддерживает двойное расширение (.XXXXXX.uc)
- В сборках OpenWrt без c-ares libcurl не поддерживает --dns-servers,
что заставляло curl использовать системный DNS (dnsmasq).
- Во время рестарта или перед стартом sing-box dnsmasq был перенаправлен
на неработающий порт sing-box 5335, из-за чего curl получал Could not
resolve host (exit 6), создавая замкнутый круг и бесконечный падеж
сервиса при скачивании подписок/списков.
- Добавлен автоматический фолбэк на DoH (--doh-url https://1.1.1.1/dns-query),
который работает без c-ares и считывает IP адреса серверов подписок/списков
напрямую в обход падающего системного DNS.
- При получении 'Another component action is already running' или сетевого
RPC-сбоя спиннер на нажатой кнопке больше не сбрасывается сразу.
- Добавлено повторное связывание (fallback lookups) с 800ms задержкой, чтобы
бэкенд успевал прописать запущенный job в state до снятия индикатора.
- При отслеживании запущенного задания теперь учитываются любые фоновые
акшны для того же компонента.
- В waitComponentActionJob добавлен минимальный порог ожидания (10с)
перед срабатыванием fallback проверки версии Tachyon. При same-release
обновлениях (одинаковый номер версии, но новый SHA) совпадение версии
больше не приводит к мгновенному (за 1с) снятию спиннера loading, пока
бэкенд на самом деле только скачивает и ставит пакет.
- Расширен список транзиентных RPC ошибок (isTransientRpcError) для
надежного перешивания перезапуска uhttpd/rpcd/ubus во время переустановки.
- Увеличен COMPONENT_ACTION_TRANSIENT_RPC_GRACE_MS до 60с.
Lock contention ('Another component action is already running') is an
expected transient state — user clicked a button while a previous action
was still in flight. Showing [error] toast confuses users into thinking
something broke. Filter it from the log watcher notification pipeline.
Also suppress DNS retry debug lines (issue #24 retry logs) from showing
as error toasts.
Added tests for both suppressed patterns.
- Guard --dns-servers behind runtime capability check (requires libcurl
with c-ares). OpenWrt builds without c-ares now skip the flag silently
instead of crashing curl with 'option not supported'.
Affects: subscription/cache.uc, components/updates.uc
- On DNS resolution failure (curl exit 6), retry with increasing backoff
(5s, 10s, 15s) before giving up, instead of immediately breaking.
Fixes transient 'Could not resolve host' during sing-box restart when
Tachyon's intercepting DNS resolver is briefly unavailable (issue #24).
- Fix stale component-action lock deadlock: pid_running now validates
/proc/<pid>/cmdline against ucode/tachyon/sh pattern + 300s TTL.
Background check_update exits cleanly on lock contention instead of
emitting [error] toasts (action.uc, state.uc, initd.uc).
- Проверять /proc/<pid>/cmdline при валидации PID (исключает ложное определение dnsmasq/sing-box как владельца блокировки при рециркуляции PID в Linux)
- Добавить автоматическую очистку зависших блокировок старше 300 секунд (TTL)
- При фоновой автоматической проверке обновлений (action == check_update) пропускать занятые блокировки без записи [error] в syslog и появления всплывающих нотификаций
- В query_llm (runtime.uc) добавлена интеграция с Anthropic Messages API (https://api.anthropic.com/v1/messages) с заголовком x-api-key
- В настройки Settings и Dashboard добавлен выпадающий выбор провайдера 'Anthropic (Claude API)'
- Добавлен русский перевод для опций AI Doctor
- Добавлена опция agent_api_token (AI Agent REST API Bearer Token) в CBI-форму настроек settings.js
- Сохраняет токен через стандартную кнопку Save & Apply в LuCI
- В Advanced Settings (Dashboard) добавлен новый блок 'AI Agent REST API (12 Endpoints)'
- Позволяет просматривать, изменять и генерировать Bearer Auth Token (agent_api_token) для REST API из веб-интерфейса
- Сохраняет токен в UCI (tachyon.settings.agent_api_token)
- kill_matching_command и цикл зачистки fd/1000 прокси в package.uc теперь пропускют процессы ucode (action.uc / updates.uc) и их предков, предотвращая выбивание воркера обновлений по SIGKILL во время prerm
- isTransientRpcError теперь учитывает 'failed to execute' в качестве транзиентной ошибки RPC при перезапуске rpcd / служб во время обновления
run_deferred_subscription_bootstrap теперь после проверки что процесс
sing-box запущен дополнительно проверяет что proxy-порт реально принимает
соединения (до 10x3s).
Без этого: если порт не готов в момент вызова — fallback на прямой
download, что провалится для URL доступных только через proxy.
При неудаче через 30s запускается retry worker (поведение сохранено).
Также: get_subscription_download_proxy_address делает одиночную проверку
порта — retry worker является safety net для edge cases.
При обновлении sing-box Tachyon перезапускается, запускает sing-box
и сразу пытался загрузить списки/подписки через proxy 127.0.0.1:4534,
но sing-box ещё не принял соединения на этом порту.
Исправления:
- updates.uc: dns_probe_passed теперь проверяет TCP-доступность proxy
вместо немедленного return true; ждёт до 30s (10 попыток по 3s)
- updates.uc: download_to_file при неудаче через proxy делает fallback
на прямой download с bootstrap DNS 8.8.8.8/1.1.1.1
- cache.uc: get_subscription_download_proxy_address проверяет что proxy-порт
реально принимает соединения, иначе переходит на прямой download
- cache.uc: download_subscription использует bootstrap DNS при прямом
скачивании подписок (когда proxy недоступен или не настроен)
Older sing-box versions fail on 'close_timeout' in MTProto inbound
(added in Tachyon to fix SIGHUP hot-reload crash, issue #15).
Instead of hard-failing, runtime.uc now detects 'json: unknown field'
errors from sing-box check, strips the offending inbound field from
all inbounds in the generated JSON, and retries the check once.
This makes Tachyon forward-compatible: new fields for newer sing-box
versions degrade gracefully on older installs without requiring manual
version detection or config changes.
Самообновление Tachyon — это компонентное задание, которое устанавливает
пакет, чей postinst и вызывает remove_component_update_cache(). Очистка
стирала каталог component-actions целиком, включая файл состояния того
самого задания, которое ещё выполнялось. LuCI опрашивал задание, не
находил его, не получал ни message, ни stderr и показывал фолбэк
"Failed to execute" поверх успешно прошедшего обновления.
Теперь очистка пропускает задания, помеченные running. Устаревшие
результаты и кэш проверок обновлений убираются как раньше. Зависшее
задание не залипнет: детект по PID и started_at помечает такие stale.
Пути каталогов состояния стали переопределяемыми через окружение, чтобы
тест мог прогнать luci-postinst, не трогая /var/run.
Коммит 3d84d96e добавил constants.TACHYON_COMMIT_SHA в get_system_status()
без соответствующего require(). В результате constants == null и каждый
вызов api.get_system_status() крашил с 'left-hand side expression is null'.
Это ломало буквально все команды Telegram-бота (/menu, /status, и т.д.)
так как каждая из них вызывает view_menu() -> api.get_system_status().
Воркер компонентного действия перенаправлял stderr в тот же файл, что и
stdout (2>&1). Действие печатает результат в stdout одним JSON-объектом,
а stderr — предупреждения apk/opkg, вывод init-скрипта при рестарте —
попадал в тот же файл после JSON. Разбор всего файла падал, построчный
фолбэк не находил объекта, и finish_component_job писал синтетический
success: false с последней строкой лога в качестве сообщения.
Особенно надёжно это ломало самообновление Tachyon: install_tachyon
рестартует сервис, будучи предком воркера, поэтому вывод рестарта
гарантированно ложился поверх канала результата.
Теперь stderr пишется в отдельный <job>.out.stderr, он же используется
как источник сообщения, когда воркер действительно умер, не напечатав
результат. Файл убирается вместе с заданием и подметается orphan-очисткой.
Заодно валидация sing-box перестала судить о бинарнике по коду возврата:
popen().close() отдаёт сырой wait-статус, а command_output отбрасывал
stdout при любом ненулевом статусе. `sing-box version` печатает версию и
может завершиться ненулевым кодом или получить сигнал от остановки
сервиса перед установкой — валидация получала "", объявляла рабочий
пакет битым и откатывала установку с exit_code 129 (128+SIGHUP). Добавлен
command_output_lenient, он же чинит показ версии в дашборде.
Добавлена проверка 15b в run_doctor_checks():
- Итерирует все включённые секции через uci_core.section_objects()
- Для каждого community_list проверяет существование .srs-файла
в TMP_RULESET_FOLDER (/tmp/sing-box/rulesets/)
- При наличии пропущенных файлов запускает tachyon list_update и
перепроверяет результат (FIXED / остались отсутствующие)
- Правильно использует section_objects() вместо get_all() который
всегда возвращает null при вызове без section_name
- get_proxy_args() теперь поддерживает опцию UCI bot_proxy_section:
если sing-box запущен и секция указана, переключает GLOBAL outbound через REST API
- Исправлен краш 'left-hand side expression is null' при обработке
callback_query без поля from (например channel_post callbacks)
- В LuCI settings.js добавлен ListValue 'bot_proxy_section' с выпадающим
списком активных секций (паттерн аналогичен download_via_proxy_section)
- Локализация: ru/tachyon.po и templates/tachyon.pot синхронизированы
- Пересборка main.js через npm run build