y83 code / журнал

Журнал релизов 0.8: часть 2 из 9

Полная ветка 0.8 из журнала проекта. Записи идут от свежих к старым, текст взят из CHANGELOG.md без правок — это тот же журнал, что читают разработчик и агент.

записей в ветке
251
приложение
185
бэкенд
59
архив сборок
7
период
19 августа 2026 — 24 октября 2026
страниц
2 из 9

журнал

Релизы ветки 0.8

Здесь записи 31–60 из 251: релизы приложения и бэкенда плюс записи архива сборок — каждая запись со своим набором правок.

backend 0.8.290

21 октября 2026бэкенд

Fixed

  • Free-модели Zen снова доводят ход Codex до конца. С 21.10.2026 free-эндпоинты opencode.ai регулярно отвечают 429 (иногда 403) server_error: Upstream request failed: Endpoint is unavailable и 400 Model is unavailable. Это не квота и не засвеченный session id, поэтому zen-прокси (8788) больше не отдаёт такой ответ наверх как есть: временные отказы повторяются (до 4 попыток, бэкофф 1→4 с, Retry-After учитывается, соединение перед повтором пересоздаётся), а отказ free-tier гейта (FreeTierError/FreeUsageLimitError) по-прежнему уходит роутеру на ротацию лейна. В лог пишется zen upstream со статусом, cf-ray, повтором соединения и адресом edge — по нему видно, какой край апстрима отваливается. Тесты: backend/internal/handlers/zen/zen_transient_test.go.

backend 0.8.287

20 октября 2026бэкенд

Fixed

  • Markdown-акценты не остаются в claims. Коротко: превращалось в claim с обрывком Коротко:; теперь маркеры вычищаются из текста утверждения (в самом ответе markdown сохраняется).

backend 0.8.286

20 октября 2026бэкенд

Fixed

  • Текст claims сохраняет инлайн-код. Вырезание кода перед проверкой цитат больше не подменяет ` @Composable пробелами в утверждениях, которые уходят верификатору и показываются в verify.verdicts`.

backend 0.8.285

20 октября 2026бэкенд

Fixed

  • Research больше не теряет синтез на собственном резерве. Проверка перед синтезом смотрит на общий дедлайн, а не на границу необязательных фаз: планировщик или второй раунд, дошедшие до synthesisAt, больше не роняют прогон в timeout за 30 секунд до конца. Первому поиску выдаётся не весь путь до резерва, а треть бюджета — на чтение остаётся время.
  • Закрыты обходы SSRF через overlay и browser_look. web_fetch с overlay:true и browser_look с url проверяют адрес до открытия WebView, оверлей идёт через search.FetchOverlay с той же проверкой. В netutil заблокированы CGNAT 100.64.0.0/10, fake-IP 198.18.0.0/15 и прочие служебные IPv4-диапазоны.
  • Убрана гонка VerifyState. startVerify возвращает снимок состояния, а фоновая проверка меняет только запись в реестре под мьютексом.
  • Citation-check не путает код и источники. Блоки кода, инлайн-код и раздел «Источники» вырезаются перед проверкой; выдуманные [N] вне диапазона попадают в Invalid, незакрытая скобка в URL не затягивает абзацы.
  • Claims и предложения корректнее. Заголовок ## Источники распознаётся, список источников не режется по точке в названии, сокращения (стр., рис., напр.) больше не склеивают обычные предложения.
  • Follow-up ссылки читаются в оригинальном виде. allowed хранит canonical-ключ → исходный URL, поэтому второй раунд не теряет www и слэш.
  • Промпт синтеза устойчив к инъекции. Экранируются любые теги </source ...> в любом регистре, заголовок страницы обрезается до 200 рун.
  • Линейный research отдаёт engine_stats, настройки AgentSearch читаются на каждый поиск, зависший слот цепочки не съедает бюджет fallback.
  • Оверлейный fetch находит страницы после редиректа (индекс по original и canonical URL) и откатывается на одиночную команду для старого APK.

backend 0.8.284

20 октября 2026бэкенд

Fixed

  • Research переживает отозванную модель в цепочке. space-bunny-free отвечает 410 ModelDeprecated, и второй слот цепочки ронял весь синтез. Страховка zen больше не берёт отозванные модели, а если настроенная цепочка уже на zen — добирает только те free-слоты, которых в ней ещё нет (раньше второй попытки не было вовсе).

backend 0.8.283

20 октября 2026бэкенд

Fixed

  • SSRF через web_fetch/браузер/research закрыт. Все URL-пути бэкенда (webget.Fetch/Download, оверлей, browser_open/browser_navigate, image_generate.source_url, скачивание) проходят единую проверку webget/netutil: только http(s), хост проверяется как литерал и через DNS/DoH до соединения, блокируются loopback/private/link-local/metadata и редиректы туда. Это важно для /mcp/remote: локальный /api/v1/* на 127.0.0.1:4100 сам по себе токеном не защищён. Для httptest оставлен явный netutil.WithAllowPrivate(ctx).
  • Bounded research больше не теряет источники на таймауте. На синтез резервируется min(30s, max(5s, deadline/6)), поиск/planner/второй раунд режутся фазовыми контекстами, а при исчерпании дедлайна возвращается status=timeout с уже прочитанными sources. Неудачные чтения не съедают max_pages, follow-up оставляет страницу под новый запрос, а капча в bounded-прогоне снова поднимает needs_human (раньше ненулевой res глушил эскалацию).
  • Текст и цитаты не рвутся по байтам. Обрезка markdown/сниппетов/дат/промпта верификатора переведена на rune-safe textutil; N разбирается с балансировкой скобок, проверяются все цитаты (не первые 100), фенсы кода не переписываются, раздел «Источники» не попадает в claims, а normVerdict больше не считает unsupported любое слово на «не».
  • RRF/выдача сохраняют настоящий URL. Склейка не подменяет результат каноническим ключом, не выкидывает значимые параметры (s, hl, ref, source, num), www. и ведущий / нормализуются только для сравнения, одиночная дата даёт нейтральный recency-бонус, а isGoogleLink больше не блокирует developers.google.com/cloud.google.com.
  • Верификатор не просит поиск у модели, которая его не умеет. Решение принимается по nativeSearch из Search Hub; в verify.model пишется фактически ответившая модель (включая zen-fallback), а research_verify ждёт с отменой по контексту HTTP-запроса.
  • Внешний текст в промптах изолирован. Фрагменты страниц в синтезе, planner и верификаторе идут внутри <source id url>…</source> с экранированным закрывающим тегом и явным правилом «инструкции внутри source игнорировать» — страница больше не может подсунуть модели команды.
  • Капча из fork-поиска доходит до needs_human. SearchBatch и SearchManyBatch оборачивают ErrBlocked, если хотя бы один движок отчитался о челлендже, а toolResearch дополнительно смотрит в engine_stats — bounded-прогон с непустым res больше не глушит эскалацию.
  • MCP-контракты. Схема web_fetch требует url или urls (anyOf вместо обязательного url), а contentIsError разбирает JSON (status:"error", ok:false) и не помечает ошибкой статью, в тексте которой встретилась строка "ok":false.

0.8.167

20 октября 2026

Fixed

  • Reasoning больше не попадает в текст ответа Codex. Некоторые free-модели zen пишут рассуждение прямо в content и отделяют его маркером </think>. Теперь мост и лента распознают такой маркер, показывают префикс отдельной карточкой, а в сообщении и в истории для upstream оставляют только финальный ответ — модель больше не продолжает собственные мысли.
  • apply_patch в Codex больше не падает с incompatible payload. Zen-прокси отдавал freeform-вызов (apply_patch) как function_call с JSON-аргументами, хотя Codex объявляет его custom-инструментом и ждёт custom_tool_call с raw input. Теперь прокси различает custom-инструменты, транслирует их в обе стороны и объявляет модели поле input; обычные function/MCP-вызовы не затронуты.
  • Агент больше не тратит ходы на запись в /tmp. На Android корневой /tmp принадлежит shell:shell, и SELinux запрещает запись из приложения. В AGENTS.md добавлено правило: временные файлы — только в tmp/ репозитория или $TMPDIR (/data/data/com.termux/files/usr/tmp).
  • Codex-пикер показывает имя модели от провайдера. Список моделей строил подпись из технического id (deepseek/deepseek-flash → «Deepseek Flash»), поэтому сохранённое имя «DeepSeek-V4.1-Flash» из экрана провайдера в окно Model не доходило. Теперь имя едет вместе с каталогом моделей и используется в пикере, шапке чата и меню треда; поиск по 4.1 тоже работает, а без имени остаётся прежний fallback.
  • VPN переживает выключение мобильного интернета. После «нет сети → через пару секунд сеть» Android выдаёт новый handle/IP, а открытый QUIC-сокет hysteria2 остаётся на исчезнувшем маршруте: TUN держит трафик, туннель молчит, и снаружи это «интернет не работает, пока не выключишь VPN». Теперь смена handle — достаточный повод сразу перезапустить hysteria2 со сбросом backoff (раньше можно было ждать до 60 с), а пре-флайт резолвит хост сервера через саму физическую сеть (Network.getAllByName), а не через мёртвый TUN — из-за этого в логах было «cell/validated, но cannot resolve info.y83.ru».
  • Картинка из remote-треда открывается, а не «Image unavailable». Вьюер обнулял и битмап, и источник для remote-пути, а файл по SSH скачивал только при пустом кэше миниатюр — в итоге Coil получал null, и кнопка Retry ничего не меняла. Теперь миниатюра показывается сразу, файл докачивается в фоне и подменяет её, Retry сбрасывает битую миниатюру и перечитывает файл, а пока идёт чтение — спиннер вместо «нет картинки».
  • MCP-картинки в remote-треде тоже читаются с сервера. Карточки image_generate/view_image собирали превью без адреса SSH-хоста, поэтому абсолютный путь результата искался на телефоне. Теперь remoteTarget прокинут в карточку, превью и полноэкранный просмотрщик.
  • Карточка SSH-сервера показывает здоровье туннеля. Вместо одной строки «connected/disconnected» — снапшот менеджера: reverse ok/down, daemon ok/down, реконнекты, keepalive и возраст проверки; последний сбой красный, пока актуален, и серый историей («last failure 12 min ago») после. Рядом с test появилась кнопка check (free port при занятом remote-порте), которая лечит канал, а снапшоты обновляются раз в 15 секунд, пока экран открыт.

0.8.166

20 октября 2026

Added

  • Чат opencode видит состояния, которые раньше проходили мимо. Репертуар v2 сверен с живым opencode 2.0.32 (роуты — без изменений): session.status оказался не легаси-именем, а живым событием (busy/retry/idle) — теперь полоса статуса показывает «повтор запроса, попытка N» с причиной, а не молчит, пока провайдер повторяет запрос. Сжатие контекста рисуется отдельным этапом «сжимаю контекст…» и сообщает об ошибке, служебные события (skill.activated, instructions.updated, synthetic) — строкой-пометкой над очередью.
  • Возврат отката доступен не только после своего действия. Граница отката хранится в снапшоте сессии (миграция кэша 12→13) и приходит событием session.revert.staged, поэтому откат, сделанный из TUI или второго клиента, виден в меню.
  • /команды и @роли в opencode-чате работают, а не выглядят как текст. Подсказки при вводе были и раньше, но /plan y83-ws уходило агенту обычным сообщением, а @plan … — просто текстом (имя роли жило только в подписи полосы статуса). Теперь /имя аргументы вызывает POST /api/session/{id}/command (сервер сам подставляет шаблон команды из GET /api/command), а @роль уезжает в промпт как agents[] — обе формы проверены живьём на 2.0.32. Неизвестное имя после слэша остаётся обычным текстом: сервер ответил бы CommandNotFoundError, и промпт был бы потерян. Разбор вынесен в parsePromptDirectives с юнит-тестами.
  • Терминал opencode ожил: PTY в чате стал интерактивным. Строка PTY в окне «Терминалы» раньше показывала только статус — теперь тап по ней открывает живой терминал поверх чата, а кнопка «Новый» поднимает свежий шелл в каталоге проекта. Рендер и ввод те же, что на экране Shell (xterm-256color, выделение, скроллбек, пинч, панель Ctrl/Alt) — общий TerminalViewport и общий эмулятор, поэтому второго набора жестов и второго владельца размера не появилось. Контракт websocket-подключения снят живьём с 2.0.32 и неочевиден: тикет connect-token одноразовый и живёт 60 с (заголовок x-opencode-ticket: 1 обязателен, иначе 403), кадры — сырые байты, а не JSON, служебный \x00{"cursor":N} даёт позицию для возобновления. Сетку окна задаёт PUT /api/pty/{id} (при создании v2 размер не принимает), причём до подключения — иначе шелл печатает приглашение не той ширины. Закрытие окна сессию не убивает: она остаётся в списке и удаляется отдельной кнопкой.

Fixed

  • Картинка из remote-треда снова видна. Миниатюра вложения в бабле пользователя и в композере собиралась без SSH-адреса сервера, поэтому путь /home/xip/... уходил в локальный загрузчик и карточка показывала «Image unavailable». Теперь remoteTarget доходит до обеих миниатюр, кэш картинок ключуется парой «путь + сервер» (одинаковый путь на разных хостах больше не подменяет чужую картинку), а полноэкранный просмотрщик для remote-файла скачивает его по SSH, а не через /api/v1/fs/download.
  • VPN переживает mobile data off/on без перезапуска вручную. При смене validated-сети сервис заново вызывает setUnderlyingNetworks, а ошибки hysteria2 network is unreachable и DNS самого сервера (invalid config: server: ... no such host) считаются подтверждённым отказом: монитор перезапускает ядро сразу, не дожидаясь трёх неудачных health-check. Старая проба больше не может «оправдать» мёртвый туннель, потому что при свежем stderr-сигнале она пропускается. В лог добавлены маркеры [vpn] underlying network -> … и [vpn-monitor] tunnel dead: hysteria unreachable.
  • Живой терминал больше не роняет приложение. Первое же подключение закрывало процесс: шаблон маркера курсора \{\s*"cursor"\s*:\s*(\d+)\s*} в виде Regex компилируется на Android ICU, а не JVM, и одиночная } для ICU — синтаксическая ошибка. Исключение прилетало из потока чтения OkHttp (ExceptionInInitializerError), то есть умирало всё приложение, а не терялся один кадр. Маркер теперь разбирается через JSON (и НИ из чего не летит наружу: колбэк кадра ловит свои ошибки), плюс в воротах появилась механическая стадия regex-icu — юнит-тесты эту граблю поймать не могут, потому что JVM такой шаблон принимает.
  • Лента истории opencode снова грузится. Страница GET /api/session/{id}/message отдаёт data и cursor соседями на верхнем уровне, а приложение снимало конверт {data: …} целиком — на выходе получался массив, разбор падал, и в сети чат выглядел как «нечитаемый ответ» при живом сервере.
  • Карточка запроса прав снова понятная. Разбор работал по схеме v1 (kind/toolName/request), а v2 присылает action/resources/save/ source, поэтому живой запрос показывался как «Permission: unknown» без текста. Теперь видно действие и ресурсы, а старые имена читаются как фолбэк; заодно починен разбор, который падал на строковом request (карточка исчезала целиком).
  • Отвеченный запрос прав не висит вечно. permission.replied/rejected снимает карточку даже когда ответ дали из TUI или второго клиента; после перезахода открытые запросы добираются из GET /api/session/{id}/permission (как уже сделано для вопрос-форм).
  • Историю чата можно поднимать вглубь. GET /api/session/{id}/message отдаёт страницу «новые → старые» вместе с курсором; раньше курсор терялся, и в ленте жила только последняя страница (около 50 сообщений). Теперь вверху ленты есть строка «Показать более раннее»: шаг за страницей, стоп — когда сервер перестаёт отдавать курсор. Направление проверено на живом 2.0.32 (сессия на 812 сообщений): вглубь ведёт cursor.next (шаг без дублей до начала истории), а previous на свежей странице отдаёт 0 — документация называет их по сторонам пейджинга, а не по времени. Поднятое сливается по id (mergeMessages) и остаётся в локальном кэше, фоновый поллинг свежей страницы больше не стирает загруженное.
  • Над клавиатурой в файлах больше нет пустой полосы. imePadding стоял на теле экрана, а нижняя панель с крошками оставалась под IME: клавиатура выезжала вместе с чёрным прямоугольником, а список обрезался до одной строки. Теперь IME поднимает саму панель (navigationBars ∪ ime), тело заканчивается ровно над ней, и список виден при открытой клавиатуре.
  • Выбор источника файлов переехал в шапку. Чипы termux/SSH больше не занимают строку над списком: текущий источник стоит в терминальном промпте (y83@files:termux ⌄) и открывает полноэкранное окно с секциями «Устройство» и «SSH-серверы» — тем же языком, что выбор модели в чате Codex. Недоступный сервер виден в списке с причиной, но не нажимается; при единственном источнике шеврона нет.
  • Удалённая сессия закрывает чат, переименование видно сразу. События session.deleted/moved/renamed обрабатываются: экран уходит к списку сессий, поток переподписывается, заголовок обновляется без ожидания idle.

0.8.165

18 октября 2026

Added

  • Файлы удалённого сервера видны и передаются из файлового менеджера. Плитка «Файлы» (хаб → Tools) получила переключатель источников: termux плюс каждый сохранённый SSH-сервер. Раньше удалённый источник включался только проектным пикером, а тот прячет файлы (оставляет одни папки), поэтому вытащить файл с сервера из интерфейса было нечем. Теперь на сервере доступны браузинг, просмотр, скачивание в Downloads, загрузка, создание, переименование и удаление — тот же экран и тот же FsSource, что для телефона.
  • Маршрут Screen.ServerFiles ожил: он держит id сервера (а не синтетического remote-агента) и читает реквизиты из remote_servers; открывается пунктом «Файлы» в меню карточки сервера (хаб → Удалённые агенты) и мостом (app_navigate {screen: "server_files"}). Туннель и codex app-server для работы с файлами не нужны — SSH держит бэкенд.

Changed

  • Скачивание папок не поддерживается (это не архив): кнопка «Скачать на устройство» в панели выделения гаснет, когда среди выбранного нет файлов, а последняя открытая папка запоминается отдельно для каждого источника.

backend 0.8.280

18 октября 2026бэкенд

Fixed

  • Zen (и Codex, и прокси) сам лечится после смены сети. Free-tier гейт opencode.ai привязывает session id к сети, из которой id впервые ушёл: после перехода на мобильную сеть (или переподключения VPN) он отвечает 403 FreeTierError — и на этом падали оба клиента, потому что id хранится долго (лейн разговора в роутере — на диске, до 14 дней; локальный id прокси — 30 минут). Теперь отказ гейта распознаётся по телу (FreeTierError / FreeUsageLimitError) и лейн разговора снимается автоматически: Router.rotateRejected (backend/internal/providers/router.go) ротирует лейн, пишет это в учёт ротаций (экран Session resets, reason auto) и повторяет запрос со свежим id; zen-прокси так же повторяет прямые вызовы (тест провайдера, curl) со свежим локальным id, а лейн, пришедший от роутера, не подменяет молча — иначе роутер не узнал бы, что id засвечен. На обычные 403 (например RegionError у beam-free) реакция прежняя — ошибка уходит клиенту. Авто-ротация ограничена кулдауном (раз в минуту на разговор): отказ гейта бывает сетевым — на мобильном операторе (18.10.2026: MTS, 91.78.41.9) free-модели отвечают 403 FreeTierError на любой свежий id, а платный лейн inference/go/v1 с того же IP отвечает 200; там ротация не помогает и незачем жечь новые сессии, похожие на абьюз. Тесты: backend/internal/providers/gate_rotate_test.go, backend/internal/handlers/zen/zen_gate_retry_test.go.

backend 0.8.278

17 октября 2026бэкенд

Fixed

  • Бесплатные zen-модели снова отвечают. Апстрим переехал: официальный клиент (каталог models.dev, провайдер opencode) ходит на opencode.ai/inference/v1, а легаси opencode.ai/zen/v1 начал отдавать на free-модели 429 FreeUsageLimitError — при том, что сам opencode на том же устройстве отвечал. zen-прокси (127.0.0.1:8788) переведён на /inference/v1; free-tier-мимикрия (форма x-opencode-session/x-opencode-request, stream: true, tools bash/read) на новом пути по-прежнему обязательна. Проверено живым запросом: /zen/v1 → 429, /inference/v1 → 200 на mimo-v2.6-flash-free. Регресс-тест держит адрес (backend/internal/handlers/zen/zen_upstream_test.go).

0.8.164

17 октября 2026

Added

  • Контекст zen-модели правится прямо в её строке. На экране «Zen» вместо чипа с диалогом — поле с кнопками «−»/«+»: шаг 100k, число можно вписать руками. Над списком — подсказка, что контекст уходит в каталог Codex и влияет на компакцию. Бесплатные модели получают дефолт 1M (то же окно, что у них на upstream), платные — 200k.

Fixed

  • Codex-роутер — оторванный от вызова результат больше не роняет тред. DeepSeek (Responses API) отвечает 400 No tool output found for tool call, если между function_call и его function_call_output встал посторонний элемент — напоминание времени, вставка инструкции, любое служебное сообщение. Разрыв попадает в историю треда и валит каждый следующий ход: новый тред в той же модели работает, старый — нет. Теперь перед отправкой провайдеру результат подтягивается вплотную к своему вызову (и в Responses-форме, и в chat-форме), пары идут в порядке вызовов. Проверено живым запросом через роутер: без вставки DeepSeek отвечает 200, со вставкой — 400.
  • Модели провайдера показываются именем, а не техническим id. Разбор GET {baseUrl}/models читал из ответа один только id, поэтому в форме добавления, в диалоге discover, в списке моделей провайдера и на его карточке висело deepseek-flash вместо «DeepSeek-V4.1-Flash». Теперь ответ разбирается целиком — имя, context_window, input_modalities и уровни reasoning приезжают в провод items, — и это же уходит в сохранённые модели: имя подписывает строку и попадает в каталог Codex (display_name), окно контекста берётся своё, а vision-флаг включается сам, если провайдер заявил картинки (раньше его приходилось ставить руками). Ответ старого бэкенда (одни id в models) читается по-прежнему.
  • Пресет DeepSeek больше не подставляет отставленные имена моделей. Платформа отдаёт в GET /v1/models два id: deepseek-flash (это DeepSeek-V4.1-Flash, 1M контекста, видит картинки) и deepseek-v4-pro; в документации deepseek-v4-flash и deepseek-v4-flash-vision-exp названы легаси-именами — их модели сняты, а запросы обслуживает та же 4.1 Flash. Пресет же подставлял в форму как раз эти два легаси-имени и окно 256k, поэтому 4.1 Flash в списке не появлялся. Теперь пресет совпадает с живым /models (и с кнопкой «Discover models», которая тянет список с провайдера).

backend 0.8.273

17 октября 2026бэкенд

Fixed

  • Смена контекста zen доезжает до чата сразу, а не после ручного рестарта. Каталог моделей Codex читает один раз при старте процесса, поэтому после правки провайдеров или контекста app-server нужно поднять заново — а проверка «каталог новее процесса» молча пропускала это: /proc/uptime на телефоне недоступен, и время старта adopt-процесса считалось от часа назад, то есть уезжало на часы вперёд. Теперь время загрузки калибруется по starttime собственного процесса (/proc/self/stat), время старта живого процесса определяется верно, а отставший app-server перезапускается сам: в токен-баре нового чата сразу видно новое окно контекста (для free-моделей zen — 950k, то есть 1M за вычетом пятипроцентного резерва под компакцию, который Codex держит сам).

backend 0.8.272

17 октября 2026бэкенд

Fixed

  • Бесплатные zen-модели больше не теряют окно в 1M. Общий дефолт 200k занижал контекст всем моделям без явного значения, и free-модели (space-bunny-free, mimo-v2.6-flash-free, deepseek-v4.1-flash-free) уходили в каталог Codex с урезанным окном. Теперь у free-моделей дефолт 1M, у платных остаётся 200k. Уже сохранённый zen.json без поля version поднимается один раз при загрузке, а записанный version: 2 закрывает миграцию — выставленное руками значение она больше не трогает.

0.8.163

17 октября 2026

Fixed

  • Экран выбора приложений VPN больше не роняет приложение. Первый же тап по приложению в фильтре «Все» клал интерфейс: выбранная строка рисовалась дважды — в секции // selected и в своей группе, — и Compose падал на дубликате ключа (Key "…" was already used), а главный поток умирал вместе с кадром. Теперь выбранное живёт только в // selected, группы показывают невыбранное (счётчики групп по-прежнему «выбрано N / всего M»), а список из PackageManager дедуплицируется по пакету.
  • Краш больше не оставляет приложение зависшим. Обработчик необработанных исключений писал строку в лог и молча возвращал управление: процесс оставался живым с мёртвым главным потоком — «висит и не отвечает» до ручного закрытия. Теперь строка пишется синхронно (очередь лога на аварийном пути не успевает), а исключение передаётся системному обработчику: приложение завершается и запускается заново.

0.8.162

16 октября 2026

Fixed

  • Туннель remote-агента больше не «дёргается». Два одновременных открытия туннеля (watchdog приложения и экран списка) поднимали два reverse-листенера на один remote:19000: проигравший оставался в памяти осиротевшим и дрался за порт бесконечно — чат показывал вечный «reconnecting», а агент терял MCP и модели. Теперь туннель регистрируется до медленного открытия (второй вызов ждёт и берёт готовый порт), осиротевший туннель замолкает сам, а одиночная неудачная попытка forward-канала больше не рвёт общее SSH-соединение.
  • Вложение уходит на машину агента, а не в телефон. В remote-треде файл, картинка и большая вставка писались в Termux-каталог, которого сервер не видит, — агент честно отвечал «файла нет». Теперь вложение выгружается по SSH на сам сервер (<home агента>/y83-ws/uploads/), и путь в промпте указывает туда же; миниатюры и ai_vision по этому пути работают как локально. Если реквизиты сервера недоступны, чип показывает ошибку с «повторить», а не подсовывает невидимый агенту путь.
  • Реконнект remote-агента больше не глушит сеть во всём приложении. Клиент codex при остановке гасил общий OkHttp-диспетчер (он делится между всеми клиентами), и после любого обрыва или смены агента async-HTTP приложения падал с «executor rejected»: шапка показывала connected, а в треде висело «retry»/«не соединено», мост переставал отвечать — до перезапуска приложения. Теперь у WS-клиента codex свои диспетчер и пул соединений (HttpClients.isolated), остановка одного клиента не трогает остальные.
  • Порт 19000 освобождается сам, sshd на сервере не забывает мёртвую сессию. После смены сети (Wi-Fi → мобильный) осиротевшая sshd-сессия на сервере держала reverse-листенер, и туннель переподключался бесконечно («tcpip-forward request denied by peer»), а агент на сервере терял MCP и модели. Теперь бэкенд при открытии туннеля ставит sshd-keepalive (ClientAliveInterval 15, ClientAliveCountMax 3, TCPKeepAlive) и снимает зависшую сессию-держателя порта через sudo; в деталях туннеля видна причина сбоя, а при занятом порте вместо «Check now» — кнопка Free port.

0.8.161

16 октября 2026

Added

  • Диктовка в непустое поле — удержание кнопки отправки. Второй жест на той же кнопке: фраза дописывается в конец уже набранного текста, а тап по-прежнему отправляет сообщение. Идёт запись — иконка красная и «дышит», как и раньше.

Fixed

  • Ошибки языка движка больше не «не сработало». ERROR_LANGUAGE_NOT_SUPPORTED говорит, что этот движок не переключает язык, а ERROR_LANGUAGE_UNAVAILABLE — что нет модели языка, и ведёт в настройки распознавания речи.
  • Многоязычный режим не обещается на старых Android. Ниже Android 14 пункта «русский + English» в меню нет — этих extras там не существует; настройка, приехавшая с нового телефона, читается как «как в системе».

0.8.160

16 октября 2026

Added

  • Диктовка «русский + English» сразу. Четвёртый вариант пункта «Язык диктовки»: движок сам определяет язык сказанного и переключает модель между ru-RU и en-US (Android 14+). Сеанс стартует с языка телефона, а если распознаватель переключение не умеет — язык остаётся прежним, и «русский»/«English» можно выбрать вручную, как раньше.

Fixed

  • Каретка после диктовки. Сказанное появлялось в поле, а курсор оставался в начале — чтобы продолжить, приходилось тапать в конец. Теперь программный текст (диктовка, заготовка промпта, путь вложения) ставит курсор в конец, а набор с клавиатуры его не переставляет.

0.8.159

16 октября 2026

Added

  • Язык диктовки в меню композера. Голосовой ввод больше не привязан к языку интерфейса: пункт «Язык диктовки» переключает по кругу «как в системе» (ru-RU/en-US по локали телефона), «русский» (ru-RU) и «English» (en-US); выбор переживает запуск. Отдельная настройка нужна потому, что распознавание идёт по одному языку, а интерфейс переключается отдельно.

Changed

  • Запись видна по цвету. Пока идёт диктовка, кнопка красная (токен error, как у Stop рядом) и по-прежнему «дышит» — запись читается без взгляда на строку статуса.

Fixed

  • С английским интерфейсом русская речь не распознавалась. Язык распознавания брался из языка интерфейса приложения (ui_language), и в режиме English диктовка уходила к английской модели — в ответ приходило «не расслышал». Теперь «как в системе» взято из локали телефона (LocaleManager.systemLocales).

0.8.158

16 октября 2026

Added

  • Голосовой ввод в Codex-чате. На пустом поле кнопка отправки становится микрофоном: тап запускает системное распознавание речи, сказанное пишется в поле сразу, по партиалам, а как только в поле появляется текст, кнопка снова отправляет сообщение. Пока идёт запись, микрофон «дышит», тап по нему закрывает запись и оставляет услышанное в поле — отправить или поправить решает человек. Язык распознавания следует языку интерфейса (ru-RU/en-US), одна фраза на тап, окно прослушки 15 секунд (зависший сервис добивается принудительно). Микрофон доступен и во время хода — сообщение уходит через очередь/steer. Сбои и отказ в доступе к микрофону показываются строкой внутри карточки композера, после «больше не спрашивать» там же кнопка «Настройки». Если системного распознавания на устройстве нет — кнопка остаётся отправкой.

0.8.157

16 октября 2026

Added

  • Большая вставка в Codex-чате уезжает файлом. Вставка длиннее 5000 символов не остаётся в поле ввода: текст пишется в ~/y83-ws/uploads/pasted-text-<дата>.txt, на месте вставки в поле встаёт путь, над полем — чип вложения с ✕. Агент читает файл своими инструментами, как при Attach: поле остаётся коротким, контекст не горит. Короткие вставки, программный текст (черновик треда, «В поле» из очереди, заготовка промпта) и вставка поверх выделения идут в поле как раньше.

Fixed

  • Лимит сообщения app-server больше не съедает текст. Сообщение длиннее 1 048 576 символов (MAX_USER_INPUT_TEXT_CHARS) сервер отклоняет целиком (invalid_params), и ход не начинался. Теперь отправка не уходит, плашка показывает длину и лимит, а кнопка «В файл» выносит текст в uploads/ — в поле остаётся путь.

0.8.156

16 октября 2026

Added

  • Учёт затрат на токены: реестр тарифов, peak/off-peak и история расходов. Цена хода больше не берётся из каталога живого opencode (GET /api/model): у кастомных провайдеров (zen, opencode-go, gemini) там нет блока cost, сервер отдавал ноль, и токен-бар молча показывал $0. Теперь есть один источник правды — ~/.y83-ws/pricing.json на бэкенде (REST /api/v1/pricing, MCP pricing_list/pricing_save/pricing_delete), приложение кэширует его в prefs, поэтому цена считается и офлайн.
  • Тарифы по времени суток. У модели есть база (у DeepSeek — off-peak) и окна с отдельными ставками (peak: 01:00–04:00 и 06:00–10:00 UTC, будни, праздники КНР — списком дат в реестре). Цена хода = сумма по запросам, каждый оценивается по своему времени и модели, поэтому ход, пересёкший границу окна, получает смешанную цену. Ставки, кэш и ступени по размеру контекста — как раньше.
  • В баре больше нет ложного нуля. $0 — только явно бесплатная модель, — — тарифа в реестре нет (раньше это выглядело как $0), ~$ — цена найдена по имени модели без провайдера.
  • Окно затрат треда (тап по токен-бару в обоих чатах): итог, ходы со временем, моделью, токенами и ценой, «без тарифа» и сверка с серверной ценой opencode v2.
  • Экран «Затраты» (хаб → AI & Agents): расход по тредам за период (сегодня / 7 / 30 дней / всё), ходы без тарифа, кнопка «Скопировать запрос агенту» со списком неоценённых моделей. История — локальная таблица turn_costs (y83-chat.db, миграция 11→12): цена хранится снимком, правка тарифа прошлое не пересчитывает.
  • Экран «Тарифы» (хаб → AI & Agents и кнопка с экрана «Затраты»): ставки по провайдерам и моделям, отметка «бесплатно», модели без тарифа, правка input/output/cache read/cache write. Реестр наполняют агент (MCP + веб-поиск) и этот экран; стартовые ставки DeepSeek V4.1-Flash и V4-Pro уже записаны, free-модели гейтвея отмечены как бесплатные.

backend 0.8.268

16 октября 2026бэкенд

Fixed

  • Research больше не падает на синтезе. Настроенная цепочка моделей смотрела на провайдера без баланса: на вопрос про QUIC транспорт у Go совпал с Python (pages_ok=4), а синтез заканчивался status: error, sources: 0 и двумя context deadline exceeded. Теперь, если вся настроенная цепочка не ответила (401, пусто, таймаут), бэкенд один раз переспрашивает бесплатные модели встроенного zen (deepseek-v4.1-flash-free → space-bunny-free), пишет в notes пометку о фолбэке, а в ответ добавляет model — какая модель фактически ответила. Цепочку, которая уже на zen, второй раз не спрашивает. У каждого слота теперь свой потолок 60 с: без него зависший провайдер съедал весь бюджет прогона, и страховка не получала времени.
  • Прочитанные страницы больше не пропадают при сбое синтеза. Линейный и bounded-пути отдавали sources: 0 вместе с ошибкой, хотя страницы уже были прочитаны: теперь sources заполняются и у частичного результата.
  • Внешний таймаут research считается от запрошенного дедлайна. Было жёстких 4 минуты на весь прогон независимо от deadline_s (до 300 с), из-за чего синтез мог получить обрезанный бюджет; теперь бюджет — deadline_s + verify_timeout_s + 60 с (потолок 10 минут).
  • Верификатор получает больше доказательной базы. Фрагмент источника обрезался на 1500 символах, поэтому утверждения, процитированные из середины страницы, помечались unsupported. Бюджет поднят до 2500 символов на источник и 6000 на утверждение (как в Python-клиенте y83r).
  • Codex больше не живёт на fallback-метаданных модели. model_catalog_json app-server читает один раз при старте, поэтому правка моделей Zen (они попадают в стор провайдеров мимо REST) и перезапуск бэкенда оставляли живой Codex со старым каталогом: в треде появлялось «Model metadata for zen/deepseek-v4.1-flash-free not found. Defaulting to fallback metadata», а контекст модели считался по дефолту. Теперь правка конфига Zen переписывает каталог и поднимает app-server, а бэкенд при старте сам перезапускает Codex, если каталог правился после старта его процесса (svreg.Status.StartedAt); каталог не перезаписывается, когда содержимое не изменилось, иначе mtime врал бы и Codex перезапускался на каждом старте бэкенда.

Changed

  • Линейный research (quick/normal) отдаёт честный status и citation_check. Раньше эти поля заполнялись только в bounded-стратегии, и агент не мог отличить ответ со ссылками от ответа без них.

0.8.155

15 октября 2026

Added

  • Терминал (Shell) возвращён новой реализацией. Экран Screen.Terminal и хаб-плитка terminal снова на месте: вендоренный termlib как ядро, SSH-транспорт напрямую (JSch), tmux window-size latest с единственным клиентом, цели — локальный Termux (127.0.0.1:8022) и серверы из БД. Настройки остались прежними (shell_font_sp, shell_term_kb_on_enter, shell_term_cols, shell_term_rows); shell_font_sp теперь Int, значение Float прежних версий читается для совместимости.
  • Диагностика моста app_shell. Status/ввод/перезапуск tmux для автотестов: работает, пока открыт экран Shell (сессией владеет экран), иначе честно отвечает no active session.
  • Терминал: панель доп. клавиш прежнего Shell. Ряд 1 закреплён (ESC, TAB, CTRL, ALT, стрелки с авто-повтором, ENTER, тумблер клавиатуры), ряд 2 прокручивается (SHIFT, зум шрифта, быстрые символы, F1–F12, PgUp/PgDn/ Home/End, C-c/C-d/C-z/C-l, вставка) и сворачивается полоской (shell_extra_keys_compact). Тап по CTRL/ALT/SHIFT — разово на одну клавишу, долгое нажатие — фиксация.

Changed

  • Инварианты терминала зафиксированы и проверяются кодом. Один владелец размера PTY (ResizeCoalescer + resizeSuspended), библиотечные жесты и выделение вместо собственного оверлея, видимость клавиатуры от WindowInsets.isImeVisible с монотонным imeRequestToken, host key до аутентификации (PinnedHostKeyRepository): расхождение с сохранённым пином блокирует соединение и не перезаписывается молча. Документ — docs/tech/terminal.md.
  • Терминал: экран не «прыгает» при входе. Плашка состояния подключения стала оверлеем внутри области терминала: раньше она была элементом колонки и на переходе Connecting → Connected исчезала, меняя высоту вьюпорта — PTY получал window-change, и tmux перерисовывал экран. Панель доп. клавиш стоит в разметке с первого кадра, стрелка «назад» — цвета темы, как на прочих экранах.

Fixed

  • Codex-чат: модель и reasoning effort принадлежат треду. Раньше выбор модели был общим на приложение, и вход в тред отправлял её на сервер как override (thread/resume {model}) — тред молча переезжал на модель, выбранную в другом чате. Теперь при открытии треда модель только читается (ответ thread/resume + нотификация thread/settings/updated, раньше игнорировавшаяся), шапка и пикер показывают модель активного треда, смена в пикере применяется к нему (thread/settings/update) и запоминается как стартовая для новых чатов; turn/start шлёт модель треда, а не глобальную. Пока сервер не ответил, показывается последняя известная модель этого треда из кэша (codex_thread_settings_<threadId>), а не выбор из другого чата.
  • Терминал: клавиатура поднимается и печатает с первого раза. Показом IME владеет один источник (TerminalImeRequests: imeWanted + монотонный imeRequestToken), termlib только исполняет; из app-слоя убран Compose SoftwareKeyboardController. Раньше два владельца дёргали IME наперегонки — клавиатура то не выезжала, то гасла сама, то выезжала без живого InputConnection (набор не уходил), и «лечилось» это несколькими перезаходами на экран. Намерение больше не снимается системным «IME не видно»: инсеты отдают ноль на первых кадрах подъёма, и это гасило клавиатуру насовсем — теперь «мигание» в окне 2.5 с после запроса чинится повторным показом (не более двух раз), а скрытие по решению пользователя (Back, кнопка панели) по-прежнему окончательно. Плюс один отложенный повтор (600 мс), когда первый запрос съела разметка. Путь IME логируется цепочкой term: ime … (в т.ч. причина, по которой клавиатура не поднялась) для быстрой диагностики. Показ IME теперь идёт только с привязанным редактором (InputMethodManager.restartInput + showSoftInput перед запросом инсетов): инсеты поднимали окно IME без цели ввода, и на свежем входе клавиатура появлялась, но не печатала — onCreateInputConnection не вызывался.
  • Терминал: клавиатура печатает в активную сессию после переключения цели. ImeInputView держал обработчик клавиш, полученный при создании View, а AndroidView не обновлял его: при переключении LocalTermux → remote в том же окне набранное уходило в прошлый эмулятор (на экране — ничего), и «лечилось» только перезаходом, который пересоздавал View. Теперь обработчик и диагностика синхронизируются в update-блоке, смена ссылки сбрасывает буфер IME, а modifierManager не остаётся от прежней сессии.
  • Терминал: «Доверять ключу» на локальном Termux больше не зацикливается. Для локальной цели ожидаемый пин всегда был пуст, поэтому проверка host key отказывала после любого подтверждения — тап по кнопке возвращал тот же экран. Подтверждённый ключ теперь сохраняется в префах (shell_term_local_host_key) и проверяется до аутентификации; смена ключа сервера получила явное действие «Доверять новому ключу» вместо тупика.
  • Терминал: вход в локальный Termux без ручной настройки ключа. Bootstrap sshd запрашивается и тогда, когда порт 8022 уже слушают: раньше в этом случае ключ приложения не попадал в ~/.ssh/authorized_keys, и подключение падало с Auth fail. Если Termux отклонил RUN_COMMAND, вместо тупиковой ошибки экран показывает готовую команду для Termux.
  • Терминал: пин host key на каждый тип ключа. У sshd есть ключи ed25519/ecdsa/rsa, а JSch перебирает их до первого принятого — из-за одного пина подтверждённый ключ другого типа выглядел как «ключ сервера изменился». Подтверждённые отпечатки копятся, при отказе наружу уходит первый отказ (по предпочитаемому типу), а не последний перебранный.
  • Терминал: стрелки и F-клавиши идут в TUI правильной последовательностью. Панель доп. клавиш слала готовые байты (ESC[A), и в vim/less с application cursor keys стрелки не работали; теперь код клавиши получает эмулятор, а байты собирает termlib по terminfo.
  • Терминал: свайп листает вывод, но не запирает ввод. Свайп превращается в тики колеса (SGR 1006) и уходит хосту — только если хост сам попросил мышь (?1000h/?1002h/?1003h): tmux с mouse on, htop/mc/yazi/vim. Раньше байты уходили любой программе на alternate screen (less, cat) и печатались у неё как текст. Там, где мышь попросили, но листать нечего (history_size == 0), tmux больше не входит в copy-mode: жёлтая плашка [0/0] в углу и «мёртвая» клавиатура выглядели как зависший терминал. Если режим прокрутки всё же открыт, любая нажатая клавиша закрывает его и доходит до панели (bind -T copy-mode Any 'send -X cancel ; send-keys'), свайп вниз до низа выходит так же (copy-mode -e).
  • Терминал: тап по TUI — клик. Мышь в mc/htop/yazi/vim работала только колесом: тап не превращался в клик и по интерфейсу программы не попадал. Теперь тап отправляет SGR-клик в клетке под пальцем (только когда программа мышь включила), а в остальных случаях просто поднимает клавиатуру.
  • Терминал: Backspace в TUI стирает, а не шлёт ^H. Обратный DelKeyMode: клавиша шла кодом 0x7F, а Delete — ^H.
  • Терминал: ESC и выход из режима в vim/TUI без полусекундной задержки. tmux стартует с escape-time 10 вместо дефолтных 500 мс (в tmux ESC — префикс биндов).
  • Терминал: UTF-8 и локаль. В канале выставляется LANG=C.UTF-8 (есть и в Debian/Ubuntu, и в минимальных образах, где en_US.UTF-8 не сгенерирован), а tmux поднимается с -u: на C/POSIX-локали рамки и кириллица рисовались мусором.
  • Терминал: в ветке без tmux возвращается эхо ввода. stty -echo при старте не отменялся, если tmux не установлен, — в обычном login-shell набор был слепым.
  • Терминал: CTRL с панели действует и на экранную клавиатуру. Панель передаёт сессии свой TerminalModifierState как modifierManager (раньше маска жила только внутри панели), а ответ эмулятора хосту (CSI 6n) больше не сбрасывает выставленный CTRL/ALT. Клавиши панели уходят в поток эмулятора, а не блокируют UI-поток синхронным dispatchKey.
  • Терминал: клавиатура уважает настройку «показывать при входе». При выключенной настройке IME больше не поднимается сам, но по-прежнему доступен по тапу и кнопке ⌨ (монотонный imeRequestToken идёт в ядро отдельно от настройки).
  • Терминал: меню выделения локализовано и берёт цвета темы. Строки terminal_selection_* переехали из third_party/termlib/res в RU/EN ресурсы приложения (дубль ключей убран вместе с res.srcDir), белые FAB «Копировать» и ⋮ заменены на токены primaryContainer/onPrimaryContainer.
  • Терминал: вход без «переездов» и дублей приглашения. PTY поднимался на первом же замере разметки — а это полукадр анимации клавиатуры и ещё не прочитанный шрифт: в логе одна сессия проходила 38x31 → 38x17 → 38x31 → 67x51 → 67x28, каждый window-change заставлял tmux перерисовать панель, и shell печатал user@host:~$ заново. Теперь ResizeCoalescer публикует размер только после «тишины», экран подключается после первого устоявшегося размера, а в префы (shell_term_rows) пишется только сетка при закрытой клавиатуре: раньше там оставались «короткие» 28 строк, и следующее подключение начиналось с них.
  • Терминал: клавиатура на входе появляется надёжно. Если первый показ IME съедала гонка с разметкой, TerminalViewport делает один повтор запроса через 600 мс — и только если клавиатуры не было вовсе, поэтому ручное скрытие не перебивается.
  • Терминал: tmux больше не трогает чужие настройки. Наши опции (window-size, alternate-screen, mouse, escape-time, history-limit) и привязки режима прокрутки теперь живут на своём сокете (tmux -L y83), а не в общем сервере tmux хоста: личные сессии и их поведение не меняются. Снаружи сессия видна как tmux -L y83 attach.
  • Диагностика терминала: видно, что ушло хостy. app_shell status теперь отдаёт imeVisible, fontSp, mouseTracking, mouseStats (sends/skips/last) и recentSizes — по ним проверяется, что подключение одно и правильной сетки, а тики колеса не уходят без жеста.
  • VPN: SSH через прямой SOCKS5 не рвётся по таймауту. DirectSocks5Proxy ставил клиентскому сокету soTimeout = 30_000 и не снимал его после handshake: idle-сессия через 127.0.0.1:1085 (backend y83runner с ControlPersist) умирала через 30 секунд тишины. Теперь таймаут снимается, включён keepalive, а живые сокеты закрываются в stop() — раньше они оставались висеть после сноса туннеля. Провал bind больше не оставляет прокси «включённым» без слушателя.
  • VPN: смена сервера и Retry применяются сразу. startReconnectLoop игнорировал запрос, пока активен прошлый цикл, поэтому переключение сервера в медленном режиме ждало до 60 с. Теперь ACTION_CONNECT/Retry/soft restart гасят старый цикл и дожидаются его выхода (Job.join) — два цикла больше не дёргают hysteria2 и TUN одновременно. TUN пересобирается под одним владельцем (монитор вместо корутинной Mutex: подъём, пересбор по политике и снос сериализованы, снос выигрывает гонку), а stopHysteria ждёт выхода процесса — порты 1080/1081 свободны до следующей попытки.
  • VPN: префы сервиса больше не затирают настройки приложения. «Желаемое состояние» и зеркало политики (per_app + набор приложений) переехали в отдельный файл vpn_service_state, который пишет только процесс :vpn: раньше его commit() в общий файл выкладывал устаревшую копию всей карты и возвращал vpn_auto_start к старому значению.
  • VPN: автозапуск поднимает туннель один раз и всегда находит сервер. Раньше повторный заход в приложение при живом туннеле перезапускал hysteria2 и рвал сессию, а включённый тумблер без выбранного сервера молча ничего не делал. Теперь connecting/connected/reconnecting пропускаются, тумблер фиксирует выбранный сервер, выбранного нет — берётся дефолтный, а причина отказа идёт в лог :vpn.
  • VPN: автозапуск работает и сразу после запуска приложения. Геттер App.vpnServerDao() (и репозиторий VPN) требовал уже созданную базу, а на холодном старте автозапуск обращается к ней первым — agentDb!! падал с NullPointerException, и туннель не поднимался (в log.txt — CRASH … App.vpnServerDao, в log-vpn.txt — ничего). Теперь DAO сам поднимает базу, а обращение автозапуска к хранилищу обёрнуто в лог-безопасный try.
  • VPN: правки сервера доходят до живого туннеля. Сохранение настроек переподключает туннель тем же сервером, удаление активного — гасит его и снимает id автозапуска (VpnManager.onServerUpdated/onServerDeleted). Заодно: YAML-конфиг hysteria2 экранирует кавычки и переводы строк (пароль/SNI с " ломали документ), смена дефолтного сервера идёт одной транзакцией Room (список не мигает без «сервера по умолчанию»), а obfs-пароль можно ввести на экране добавления сервера, а не только при редактировании.

0.8.154

15 октября 2026

Removed

  • Экран терминала вырезан целиком. Удалены terminal/ (сессия, SSH-канал, tmux-бутстрап, подготовка локального Termux, геометрия, сканер alt-screen, модели жестов/пинча/колеса) и ui/terminal/ (экран, панель, настройки, вью-модель), вместе с точками входа: Screen.Terminal, хаб-плитка terminal, группа моста app_shell, роуты скриншотов terminal и неиспользуемые C-исходники PTY. Причина: слой терминала тащил дефекты прежней реализации (два владельца размера PTY, свой оверлей жестов, залипающее состояние IME). Возврат — только новой реализацией по ТЗ docs/tech/prompt-terminal-argon.md. Проверка host key сохранена для экрана удалённых агентов (data/ssh/HostKeyPin).
  • Shell Chat вырезан из проекта целиком. Экран, лента команд, парсер OSC 133, снапшоты вывода, композер, «Спросить ИИ», полноэкранный просмотр вывода и транскрипт в Room (shell_transcript, миграция 15 → 16) удалены: совмещение ленты и живого PTY в одной сессии ломало терминал. Возврат чат-слоя запрещён и стережётся стадией terminal-isolation.

Changed

  • Терминал переписан как отдельный слой. Новые TerminalSession / TerminalSessions / SshChannel / TmuxBootstrap не знают ни о командах, ни о ленте: PTY-байты читает только эмулятор, сетку задаёт вьюпорт, TUI/мышь/ cwd приходят из tmux-пробы и OSC 7. Выделение, жесты TUI, доп. клавиши, пинч-зум и host key до userauth сохранены. Bridge screen id: shell_chat → terminal (скриншот/навигация моста), shell_output убран.

Added

  • Терминал (Shell), жесты в TUI. Долгое нажатие начинает выделение текста (анкор — клетка под пальцем), протяжка ведёт его за пальцем, отпускание копирует в буфер: в mc/htop/nvim выделить текст было нельзя — тач-поток в TUI целиком принадлежал слою жестов. Двойной тап поднимает и скрывает клавиатуру (обычный тап в TUI с мышью уходит кликом).
  • Терминал (Shell), панель доп. клавиш. Чип ⌨ переехал в закреплённый первый ряд — во втором ряду он пропадал при compact, и клавиатуру в TUI с мышью было не вернуть. Во второй ряд добавлены быстрые символы / - | ~ : * _, отправляемые с учётом залипших Ctrl/Alt.

Fixed

  • Терминал (Shell), геометрия сетки. Финальный размер вьюпорта больше не задерживается цепочкой дебаунсов (200 + 150 + 250 мс): пауза «размер устоялся» одна (VIEWPORT_SETTLE_MS = 100) и живёт во вьюпорте, коалесинг — один (RESIZE_COALESCE_MS = 120) в сессии. Нижние строки nvim/mc перестают уезжать под панель клавиш: публикацию размера теперь триггерит измеренный размер контейнера (Modifier.onSizeChanged), поэтому сворачивание/разворот второго ряда ExtraKeysBar и показ/скрытие клавиатуры гарантированно доносят актуальную сетку до PTY.
  • Терминал (Shell), мышь без tmux. Клик и колесо уходят в программу и на сервере без tmux, но только когда процесс сам запросил SGR-кодировку (DECSET 1006): в другой кодировке координаты для программы — обычный текст. Флаг SGR берётся из потока PTY (AltScreenScanner) и учитывается и при отправке, и в решении «тап = клик».
  • Терминал (Shell). tmux больше не подавляет smcup/rmcup (terminal-overrides ',*:smcup@:rmcup@' убран): клиентский эмулятор честно переключается в alternate screen, поэтому полноэкранные TUI (mc, htop, nvim, lazygit) больше не рисуются поверх primary screen с обрезанными нижними строками и мусором рамок, а вход/выход из TUI виден мгновенно — по CSI ?1049h/?1049l, не дожидаясь опроса пробы. Сессия tmux переживает обновление: старый сервер мигрируется на коннекте (set -sgu terminal-overrides → ,*:Tc) без перезапуска и без потери панели.
  • Терминал (Shell), конфиг tmux. Добавлены focus-events, xterm-keys и window-size latest: TUI получают фокус и модификаторы клавиш, панель следует за размером активного клиента.
  • Терминал (Shell), каталог в промпте. Промпт отдаёт OSC 7 (file://host/path), сканер его разбирает: на серверах без tmux cwd в шапке обновляется сразу (раньше оставался пустым), под tmux работает #{pane_current_path} из пробы.
  • Терминал (Shell), host key. Ключ сервера сверяется на key exchange, до аутентификации: при расхождении с запиненным JSch рвёт подключение, и пароль на чужой хост не уходит (раньше StrictHostKeyChecking=no, а проверка после connect() давала только предупреждение). Сессия встаёт в ошибку без ретраев, а сохранённый отпечаток больше не перезаписывается молча: конфликт с доверенными ключами бэкенда показывается на карточке сервера и ждёт подтверждения (Test → Trust new key).

0.8.153

14 октября 2026

Fixed

  • Единый профиль, продолжение. Cookie-jar: куки с Path: "/" больше не теряются на адресе без пути (корень сайта), запись атомарная (tmp+rename), а CLI и сервер ходят одним файлом ~/.y83-ws/webget-cookies.json (двойная запись убрана). sec-ch-ua-model: "K" (заглушка android-UA) больше не отправляется, хинты берутся из реального устройства (navigator.userAgentData), а TLS-профиль выбирается по хосту — desktop-спуф одного сайта больше не меняет отпечаток мобильных запросов.
  • Поиск. DDG-клиент не форсирует свой UA (единый профиль важнее), fork-поиск снова использует AgentSearch, cdn-proxy ходит по схеме исходного URL через DoH-клиент.
  • Браузер (ожидание человека). Экран ожидания капчи стал неблокирующим баннером: страница не перекрывается, проверку можно пройти, кнопка «Готово» синхронизирует профиль.
  • Жизненный цикл. Уход с экрана браузера больше не гасит скрытую сессию агента и headless-фетч (учёт потребителей сервиса); краш рендерера Chromium (onRenderProcessGone) больше не убивает приложение — вкладка помечается ошибкой и пересоздаётся.
  • Мелочи браузера. Стрелки поиска по странице работают (findNext), localStorage сессии восстанавливается уже на целевом URL, blob:/data: скачивания сохраняются, а обычные — с куками и UA, диалоги видимой вкладки пишутся в журнал и управляются политикой, кастомные схемы (mailto:, tel:) уходят во внешние приложения, переходы внутри страницы не перезапускают загрузку (Referer/POST/история целы).
  • Стелс. Антибот-скрипт ставится как document-start (плюс фолбэк) и работает на всех путях — видимый браузер, скрытая сессия, headless-фетч и оверлей; navigator.mimeTypes и языки больше не выдают WebView.

Added

  • Телеметрия видимого браузера. browser_console, browser_network, browser_dialogs, browser_clear_data работают и при hidden=false; browser_get_state отдаёт readyState, inflight, consoleErrors/Total, networkFailed/Total, dialogsTotal.
  • Нативный ввод. Клик отправляется настоящим тапом (isTrusted), текст — через нативный сеттер прототипа с InputEvent, поэтому формы React/Vue/contentEditable заполняются (в ответе видно via: touch|js).
  • Set-of-Mark. browser_shot {annotate:true} и browser_annotate нумеруют видимые интерактивные элементы (meta.marks), клик и ввод принимают метку (selector:"@12" или mark:12).

0.8.152

14 октября 2026

Added

  • Единый профиль WebView→Go (identity_sync). UA и куки реального WebView телефона уезжают в клиент поиска и чтения (bridge-команда browser_identity, файл ~/.y83-ws/browser-identity.json плюс общий jar): после ручного прохождения проверки запросы из Go идут под тем же устройством, а не как «второе устройство с того же IP». Подтягивается автоматически при первом челлендже (TTL 10 минут, один повтор) и явно — инструментом identity_sync.
  • Капча как human-in-the-loop. Когда все движки упёрлись в проверку, web_search/research отвечают status=needs_human и сами поднимают браузер на страницу выдачи (browser_wait_for_user с локализованным текстом); после «Готово» профиль синхронизируется, и повторный вызов проходит. Прямой Google-скрейп из Go — только явным via:"http", в auto-каскад не включён.
  • HTTP-отпечаток Go-клиента под единый профиль. Клиент больше не ходит «голым»: sec-ch-ua* выводятся из фактического UA (не бывает UA Chrome/153 без хинтов), порядок заголовков фиксируется как у Chrome (Header-Order: вместо алфавитной сортировки fhttp), а TLS-профиль подбирается по версии Chrome из синхронизированного UA (153 → Chrome_146, 131 → Chrome_131). Высокоэнтропийные хинты уходят только после Accept-CH; ротация профиля переиспользует общий cookie-jar, поэтому куки не теряются.
  • Прогрев Google и кука согласия. Перед прямым скрейпом SERP клиент раз в 10 минут открывает главную Google (куки AEC/NID + активация Accept-CH), а кука SOCS снимает редирект на consent.google.com; выдача открывается как переход с главной (Referer + Sec-Fetch-Site: same-origin).
  • Новые движки как эксперимент. engine:"startpage" (POST-форма выдачи) и третий шаг DDG — lite через POST (обходит vqd-токен и статус 202). В auto- каскад не включены до замера.
  • Кулдаун и пейсинг движков. После капчи/челленджа/согласия движок уходит в паузу на 4 минуты (google-http, google-overlay, ddg, startpage, …), между запросами к scrape-движку выдерживается 600 мс. Кулдаун снимается сам, когда человек прошёл проверку и профиль синхронизировался.
  • Чтение страниц отдаёт markdown. web_fetch (и оверлейный путь) возвращает заголовки, списки, цитаты и ``-блоки кода вместо схлопнутой строки, плюс title`; дефолтный бюджет — 5000 символов; фолбэк на простой текст, если разбор «съел» содержимое.
  • Окно согласия — отдельная причина needs_human. needs_human.reason теперь captcha или consent (текст «нажми „Принять все"», ключ bridge_wait_consent); эскалация и синхронизация профиля те же.
  • browser_identity отдаёт UA того сеанса, который открывал хост (uaSource: host|tab|default): проверка, пройденная в скрытой сессии оверлея, больше не расходится с UA видимой вкладки. В desktop-режиме патчится и navigator.userAgentData — хинты больше не спорят со строкой UA.

Fixed

  • Object не работал из-за системы координат тапа (настоящая причина). Java-обёртка InteractiveSegmenter не выставляет proto-поле normalized точки интереса (в region_of_interest.proto это bool normalized = 3, у proto3 дефолт false), а AnnotationOverlayCalculator при normalized=false берёт x/y как пиксели и обрезает дробную часть. Приложение отдавало 0..1 — то есть тап всегда попадал в пиксель (0,0), в левый верхний угол, и модель отвечала мусором: то категорийной маской «всё изображение» (pct=1.000, «Too big»), то пустым/нулевым confidence («Nothing selected»). Теперь точка переводится в пиксели картинки перед вызовом segment().
  • Резерв по категорийной маске снова подключён. Раньше она была отключена в опциях, поэтому при пустом confidence падение заканчивалось ошибкой no masks без объяснения. Теперь оба выхода запрошены, а в лог пишется, что именно пришло: источник, формат, размер буфера, пик, точка тапа и pct.
  • Оверлей больше не отдаёт заглушку капчи как содержимое. Короткая страница с маркерами челленджа (recaptcha, «подтвердите, что вы не робот», anomaly) помечается капча/челлендж: движок уходит в engine_stats как заблокированный, каскад идёт к следующему эшелону, а web_fetch возвращает ошибку вместо мусора.

Security

  • Секреты в базе приложения больше не лежат открытым текстом. SSH-пароль (remote_servers.authRef), пароль и obfs-пароль VPN (vpn_servers.password/obfsPassword) и vision-токены (agents и projects.visionToken) шифруются AES-GCM на ключе AndroidKeystore и лежат в базе как enc1:…; при чтении расшифровываются прозрачно, поэтому экраны, терминал и сторож туннелей работают как раньше. Записи, сделанные прежними версиями, перешифровываются на устройстве при первом запуске.
  • Мост не отдаёт секретные колонки. Ответы sqlite_query и SELECT-ветки sqlite_execute показывают <redacted> для колонок вида password, authRef, obfsPassword, …token, secret, api_key — раньше через инструмент моста можно было вычитать SSH-пароль из y83-ws.db.
  • Пароли ушли из логов. Генератор VPN-конфига больше не пишет в лог hysteria2.yaml и obfs-пароль (их было видно через app_log).

Notes

  • Запись [0.8.151] ниже про «Object теперь действительно выбирает объект» была преждевременной: та правка чинила только разбор буфера маски, а тап по-прежнему уходил в угол.

0.8.151

14 октября 2026

Fixed

  • Чтение выхода модели приведено к формату графа. magic_touch отдаёт float32 confidence-маску («segmented mask with pixel values in float32 format»); читаем её буфер без подмены формата (ByteBufferExtractor.extract, выравнивание строк считается по размеру буфера) и пишем в лог источник, формат, размер буфера, пик и pct — битое чтение больше не проходит молча.
  • Превью картинок в правке и в списках снова видны. Coil грузил file:///data/data/com.termux/..., а это приватная папка Termux: другому приложению туда нельзя, поэтому пустыми были и миниатюры области/результата, и сетка референсов. Картинки хоста теперь идут через локальный бэкенд (GET /api/v1/fs/download), а он отдаёт честный Content-Type и Cache-Control вместо application/octet-stream.
  • Маска: тап по объекту больше не выбирает весь холст. Порог сегментации считался от глобального максимума уверенности по всему кадру, из-за чего в маску попадал фон; теперь порог берётся от уверенности в точке тапа, из бинарной картинки остаётся компонента связности под пальцем, дырки заливаются, а компонента больше 85% кадра отклоняется как фон (MaskMath, покрыт тестами).
  • Повторный тап в режиме Object заменяет выделение, а не дорисовывает вторую маску поверх первой.
  • Save mask больше не «не нажимается». Кнопка называлась Save mask, но никуда не вела: путь к PNG копировался в буфер и терялся. Теперь это Edit — маска сразу уходит в правку, статус и ошибки видны в самой панели (тост уезжал под неё и его не было видно).
  • Размер маски всегда совпадает с исходником. Исходник нормализуется один раз: EXIF-поворот применяется, длинная сторона ужимается до 2048 px — раньше маска могла быть «боком» и другого размера, и backend её отклонял.
  • Сегментатор больше не поднимается при открытии картинки. Модель 6.2 МБ читается только когда реально ткнули объектом.

Added

  • Понятный флоу правки. Панель маски получила primary-кнопку «Продолжить» и состояние «Готовлю картинку…»; окно Edit пересобрано в секции «Область / Что изменить / Стиль / Настройки» с липкой кнопкой «Применить» внизу, прогрессом, отменой и действиями результата.
  • «Сформулировать» промпт. Кнопка переписывает простой текст в подробный английский промпт Krea через POST /api/v1/webai/ask (search=false); raw-промпт всё равно можно применить как есть.
  • Сетка референсов с превью. Пикер показывает миниатюры, источник («Мои генерации» / «Вложения») и мультивыбор; технические edit-*-mask.png и edit-*-source.png в список не попадают.
  • Полный флоу правки в приложении. Инструменты маски Object / Brush / Box (плюс Erase), затем Edit открывает окно правки: промпт, модель, denoise, seed, krea_4step, post_upscale, две LoRA с силой. Job уходит напрямую через POST /api/v1/image-jobs — без хода агента; прогресс, результат и кнопка «Open result» (результат грузится в просмотрщик).
  • Референсы прямо в окне правки: «Add reference» открывает выбор картинки из uploads/ и скачанных генераций, для каждой — рецепт style_refs и сила. Это тот путь, которым Krea 2 переносит стиль/палитру/свет с картинки (а не img2img).
  • GET /api/v1/image-jobs/options — единый каталог окна правки: модели, рецепты style_refs и список LoRA (internal/imagegen/loras.go).
  • Скилл y83-imagegen: честный раздел про img2img (у Krea 2 image-to-image — это референс, а не сохранение пикселей) и описание ручного пути маски в приложении.

0.8.150

13 октября 2026

Fixed

  • Маска в просмотрщике картинок теперь действительно работает. Сегментация читала выход MediaPipe через BitmapExtractor, а он принимает только картинки-Bitmap: для буфера графа (VEC32F1) он бросает исключение, оно молча съедалось — тап не выделял ничего, а «Save mask» оставался выключенным. Теперь маска читается ByteBufferExtractor по формату выхода (float и байтовые категориальные маски), раздувается на 2% и приводится к размеру файла-исходника — раньше брался размер кэша миниатюры, и backend отклонял маску другого размера («mask is 512x512 but the source is 1024x1024»).
  • Тап по картинке больше не теряется на зуме. Короткое касание без сдвига определяется в самом жесте — панорама увеличенной картинки съедала события у detectTapGestures; двойной тап-зум в режиме маски выключен.

Added

  • Кисть как второй инструмент маски (Object / Brush): обвести нужную область пальцем можно и без ML — маска делается, даже если модель не поднялась. В режиме маски один палец рисует, два — зум и панорама, Reset снимает выделение; «Save mask» кладёт чёрно-белый PNG размера исходника в ~/.y83-ws/uploads/ и копирует путь для mask_path.

0.8.149

13 октября 2026

Added

  • Маска-картинка для image_edit: mask_path и mask_url. Раньше область правки задавалась только прямоугольниками mask_region; теперь можно передать настоящую PNG-маску (того же размера, что исходник, белое = перерисовать), и backend протащит её тем же путём, что исходник и style-референсы: прочитает или скачает файл, проверит формат, размер и лимит 5 МБ, инлайнит base64. mask_path/mask_url взаимоисключающие с mask_region; в ответе появились mask_sha256, mask_bytes и mask_dimensions. Media API размер маски не сверяет сам, поэтому проверка живёт у нас — иначе кадр перерисовывался бы по чужим координатам.
  • Тап-по-объекту в просмотрщике картинок. В шапке полноэкранного просмотрщика есть режим маски: тап по объекту запускает MediaPipe Interactive Segmenter (модель magic_touch, 6.2 МБ в assets, работает офлайн) и рисует поверх картинки полупрозрачный силуэт; дополнительные тапы уточняют выделение, «Save mask» кладёт чёрно-белый PNG в ~/.y83-ws/uploads/ и копирует путь — его остаётся вставить в чат и отдать агенту как mask_path. Vision для границ объекта больше не нужен.

Changed

  • Скилл y83-imagegen переписан под референсы. Явное правило: стиль, персонаж, палитра, свет, фактура и фон переносятся через style_refs (Krea 2 читает референс vision-токенами, а классический img2img перерисовывает кадр целиком и теряет лицо, лого и текст), а маска — только по прямой просьбе точечной правки. Добавлены таблица 32 recipe «что брать из референса», ориентиры по strength, таблица LoRA с жанрами и силами, правила concept_sliders и запрет самовольного батча и авто-проверки результата (это шло из generic-скилла imagegen).