-
Состояние сессии, отметки о непрочитанном и то, работает ли сессия до сих пор, теперь определяются на бэкенде, и все клиенты видят один и тот же ответ. Раньше каждый клиент выводил всё это из того, что случайно наблюдал сам, поэтому «непрочитано» на деле означало «не прочитано в этом окне»: сессия, прочитанная в браузере, оставалась непрочитанной в настольном приложении, а динамически расширяющийся фильтр по состоянию снова и снова возвращал эту строку в общий список, откуда её не убирало даже «Обновить состояние». События состояния к тому же регистрировались только после того, как клиент сам запускал хотя бы одну сессию, поэтому только что подключившийся браузер показывал точки у сессий, которые открыл сам, и ничего — у всех остальных. Теперь бэкенд держит по одной достоверной записи на сессию — агент, его состояние, жив ли процесс и признак непрочитанного, — отвечает на пакетный запрос, когда клиент подключается или переподключается, и рассылает каждое изменение всем. Клиенты сообщают, что они наблюдают; выводы делает бэкенд. Прочитанная на телефоне сессия снимает отметку и в настольном приложении, только что подключившийся браузер показывает правильные точки у сессий, которые он никогда не открывал, а правила арбитража, жившие раньше во фронтенде, переехали вместе со своими обоснованиями и своими тестами: хук, сообщивший о себе хотя бы раз, закрывает дорогу любым догадкам по сырому выводу; непрерывный вывод означает занятость только для сессии, у которой есть агент, которая не является Codex и по которой ещё нет достоверного сообщения; а удержание в 1200 мс не даёт только что начатой задаче быть отменённой событием завершения, пришедшим сразу следом. Исключение — чтение экрана: ему нужна отрисованная сетка, а она существует только в клиенте, поэтому прочитанное сообщает тот клиент, которому принадлежит размер терминала, а бэкенд решает, принимать ли это сообщение, — сообщение от любого другого клиента отклоняется. Если что-то из этого пойдёт не так, значение frontend у ключа vlx-arbitration в localStorage возвращает арбитраж прежней цепочке во фронтенде.
-
Перезапуск сессии больше не закрывает её вкладку на другом клиенте. Событие pty://killed не несло с собой никаких данных, поэтому другая сторона не могла отличить перезапуск от закрытия: она считала закрытием всё подряд, убирала панель, а потом отзеркаливала эту раскладку обратно. Теперь событие сообщает, какой клиент завершил процесс и почему, поэтому при перезапуске панель остаётся на месте и ждёт нового процесса. Отсутствующая или незнакомая причина по-прежнему считается закрытием: оставить панель открытой для сессии, которая уже не вернётся, — значит держать на экране мёртвый терминал, а это хуже, чем закрыть ту, что собиралась перезапуститься.
-
Браузер, подключившийся к настольному приложению сразу после восстановления рабочего пространства, больше не запускает все сессии по-настоящему. При восстановлении рабочего пространства вместо запуска процессов рисуются карточки-заглушки, но это решение существовало только в настольном приложении; браузер шёл за отзеркаленной раскладкой, не мог отличить «не работает» от «работает, просто здесь ни разу не открывалась» и монтировал терминалы — а смонтировать терминал значит его запустить. Лист, пришедший с чужой раскладкой, теперь рисуется заглушкой, если бэкенд говорит, что процесса за ним нет. Открыть сессию самому по-прежнему означает намерение её запустить, а терминал, на который вы смотрите прямо сейчас, при завершении процесса никогда не подменяется карточкой — вы ведь ещё можете захотеть прочитать то, что он вывел.
-
Изменение настройки немедленно доходит до остальных клиентов. Достоверные настройки всегда хранились на бэкенде, но менял он их молча, поэтому другой клиент узнавал об изменении только при следующем запуске. При удалённом подключении это не просто расхождение в отображении: выключить «динамически расширять фильтр по состоянию» на одном клиенте было бесполезно, пока другой продолжал добавлять строки в общий список, а клиент с меньшим лимитом живых вкладок выгружал фоновые вкладки у всех остальных. Теперь запись настройки рассылает всем, какой ключ изменился, и каждый клиент перечитывает его тем же путём, каким читает при старте, поэтому правила, скрывающие защищённые значения от удалённых вызывающих, продолжают действовать: рассылка несёт только имена ключей и никогда — значения. Телефоны, которые раньше настройки ни отправляли, ни получали, теперь тоже в этом участвуют.
-
Браузер дожидается отзеркаленной раскладки, прежде чем восстанавливать свою. У удалённого окна два источника раскладки — своя, из localStorage, и та, которую в зеркальном режиме присылает хост, — и та, что приходила первой, затиралась другой. Показать сначала локальную стоило дороже, чем мелькание на экране: смонтировать лист с терминалом значит запустить настоящий процесс, и для сессии, чей процесс уже завершился, это означало поднять оболочку, на которую никто никогда не посмотрит, — и она потом оставалась висеть, потому что браузер свои терминалы отсоединяет, а не завершает. Теперь браузер ждёт, пока пройдёт первое согласование, и только после этого что-то восстанавливает, но не дольше двух секунд; если бэкенд медлит или недоступен, он возвращается к локальной раскладке, а не сидит с пустым окном. Телефоны и любой клиент с выключенным зеркалированием проходят сразу.
-
SSH-подключение может включить зеркальный режим для той службы, которую оно поднимает. Переключатель живёт в панели удалённого доступа, но SSH поднимает на дальней машине службу без интерфейса, и никакой панели там нет. Удерживайте Option при щелчке по «Connect remote» — и в форме SSH появится флажок «Mirror UI across clients», по умолчанию снятый. Значение едет вместе с подключением и живёт в памяти службы, а не записывается в базу данных дальней машины: когда вы вдобавок используете базу данных самого удалённого рабочего стола, одно SSH-подключение не должно незаметно переключать тумблер в чужой панели. Выбор запоминается отдельно для каждого хоста, поэтому та же машина, выбранная из истории, приходит вместе с ним. Чтобы переиспользовать уже работающую службу, теперь должны совпасть версия, режим данных и зеркальный режим — если различается хотя бы одно из трёх, старая служба заменяется, а это завершает работающие на ней сессии; поэтому эта настройка и спрятана за Option, рядом с переключателем базы данных.
-
Клиент, который повторяет чужую раскладку, теперь об этом сообщает. Раньше вкладки и разделения на ведомом клиенте перестраивались сами собой, и на экране ничто не объясняло, откуда взялось изменение. Теперь в заголовке окна есть значок «Mirrored», а при наведении показывается пояснение. У хоста такого значка нет — у него есть сам переключатель.
-
Файлы теперь можно переносить между вашей машиной и той, где работает терминал. Удалённый доступ показывал файлы удалённой машины, но забрать один или положить туда свой было нечем — оставалась только команда в терминале. В панели файлов появились «Скачать» в контекстном меню файла и «Загрузить» в заголовке, а перетаскивание файлов с рабочего стола на строку папки отправляет их туда. Оба направления идут по тому же аутентифицированному соединению, что и всё остальное, поэтому одинаково работают из браузера в локальной сети, с телефона и из окна удалённого подключения. Передача идёт частями, под деревом показывается очередь с прогрессом, и она продолжается, пока вы смотрите другую панель. «Скачать» — обычная ссылка на загрузку, которой занимается собственный менеджер загрузок браузера: он пишет на диск по мере получения, показывает скорость и оставшееся время, умеет ставить на паузу и продолжать — при любом размере файла и в любом браузере, включая телефон. В ссылке лежит билет, выписанный на один этот файл и действующий несколько минут: этот сервер держит учётные данные в заголовке, а браузер, открывающий ссылку, заголовков не отправляет. Загрузка сначала пишется под временным именем и переименовывается только после завершения, так что прерванная передача никогда не оставит наполовину записанный файл там, где должен быть целый; занятое имя отклоняется до того, как что-либо будет передано. Загрузки показывают скорость и оставшееся время и переживают обрыв связи: неудавшийся кусок ждёт и повторяется около минуты, каждый раз спрашивая у сервера, докуда на самом деле дошёл временный файл, вместо того чтобы слать заново кусок, который мог уже записаться. Даже сдавшись, эти байты сохраняются: перетащите тот же файл в ту же папку — и передача продолжится с места остановки, в том числе после перезагрузки страницы; частичный файл выбрасывается только при отмене.
-
Сессии Antigravity и Copilot называются по своему первому сообщению. Оба агента отсутствовали в автоматическом переименовании, из-за чего в боковой панели выстраивались ряды «Antigravity 1, 2, 3». События хуков Antigravity вообще не несут пользовательского текста — только идентификатор беседы и путь к расшифровке, — поэтому первое сообщение читается из самой расшифровки; идущий за ним блок метаданных в заголовок не попадает. События Copilot к тому же не несут имени события и различаются по своей форме, поэтому тело с запросом и без имени инструмента теперь считается отправкой сообщения — а значит, промпт, с которым запускается сессия, и вызовы инструментов в заголовок справедливо не попадают.
-
Сессия Antigravity открывается заново вместе со своей историей. Для возобновления нужен идентификатор беседы, а разбор, который вытаскивает идентификатор сессии из аргументов запуска, не узнавал того написания, которое использует Antigravity, поэтому --conversation=<id> было не на что опереть и каждая переоткрытая сессия поднималась пустой.
-
vspawn --yes создаёт дочернюю сессию без карточки подтверждения. Тем, у кого включено подтверждение перед созданием дочерних задач, приходилось прощёлкивать карточку для каждой дочерней сессии подряд. Флаг — его же можно написать как -y или --no-confirm — пропускает карточку для этого одного вызова и запускает сессию с настройками по умолчанию. Саму настройку он не меняет, поэтому следующий запуск без него снова спросит.
-
Поле модели на карточке дочерней задачи принимает всё, что вы наберёте. Раньше это был обычный выпадающий список, поэтому выбрать можно было только модели из него, — а агент понимает куда больше идентификаторов: имена с датой вроде claude-opus-4-6, имена с префиксом поставщика, псевдонимы, настроенные локально. Теперь это текстовое поле, а известные модели висят рядом в выпадающем списке как быстрый выбор. Список — это подсказка, а не белый список: передаётся ровно то, что вы набрали, пустое поле означает, что --model не передаётся вовсе, а сам список фильтруется по мере набора и сворачивается, когда произвольный идентификатор не совпадает ни с чем.
-
Один выпадающий список на всё приложение. Выпадающие списки по всему приложению были больше десятка раз скопированы с одного и того же кода, а потом разошлись между собой: три разных фона панели, четыре тени, три цвета подсветки под курсором, высота кнопки в 26, 28 и 32 пикселя и галочки у выбранных строк, которые нужны только списку с множественным выбором. Теперь на одном компоненте держатся выбор веток при слиянии, выбор языка, оболочки по умолчанию и шрифта в настройках, выбор агента, диалоги рабочих деревьев (worktree), выбор типа агента у новых и восстановленных сессий и последний нативный select в модальной форме. Вместе с ним появилось управление с клавиатуры, которого не было ни у одного из них: стрелки перемещают выбор, Enter выбирает, Escape закрывает список, не закрывая диалог за ним, Home и End прыгают в начало и конец. Два меню в строке состояния ведут себя как раньше, а фильтр по состоянию в боковой панели сохраняет свои галочки — он и правда с множественным выбором.
-
Пароль в панели удалённого доступа можно показать. Это было голое поле пароля, и набранное в нём было не разглядеть; кнопка с глазом существовала, но только внутри файла самой панели подключения. Теперь оба места используют один компонент, а показанное состояние сбрасывается при закрытии панели.
-
Селектор IP больше не выглядит как системный элемент управления. Это был нативный select, который WKWebView оформляет системным образом, и на тёмной панели это смотрелось плохо — та же претензия, что и к заменённым выше выпадающим спискам. Теперь он использует общий компонент, а его подпись сокращена до «IP»: соседний текст и так объясняет, зачем он нужен.
-
Проверка обновлений каждый раз действительно спрашивает сервер. Клиент, оставленный работать надолго, залипал на первой увиденной версии: найдя 0.1.101, он продолжал предлагать 0.1.101 и после выхода 0.1.104, а «Проверить обновления» лишь открывало заново тот же диалог, потому что старый код выходил сразу, если уведомление уже висело. Теперь каждая проверка — настоящий запрос. Более новая версия заменяет уведомление на экране, та же самая версия или идущая загрузка оставляют его как есть, а сервер, который вообще не сообщает об обновлении, убирает устаревшее уведомление — версию отозвали или вы тем временем поставили её сами. Кнопка «Скачать вручную» теперь открывает страницу загрузки на сайте; раньше она отдавала собственный пакет механизма обновления, который распаковывается на месте и вручную не устанавливается.
-
Полноэкранный оверлей с ошибкой больше не появляется при быстром уничтожении терминалов. Область просмотра xterm планирует синхронизацию области прокрутки при создании и ещё одну при сбросе и ни ту ни другую не отменяет при уничтожении, поэтому терминал, открытый и закрытый в пределах одной задачи, — а именно это и происходит при перестройке дерева сессий во время удалённого подключения — всё равно выполнял эти callback, находил уже очищенный рендерер и падал с исключением. Исключение прилетает из таймера, куда не дотягиваются ни try/catch, ни граница ошибок, поэтому оно перехватывается глобально и распознаётся узко: проглатывается как безобидный и записывается в журнал запросов только тот стек или то сообщение, которые называют именно эту синхронизацию и вдобавок упоминают рендерер или его размеры. Настоящие сбои по-прежнему поднимают оверлей.