0.10.0 26 мая 2026 г. + наши доработки

Удалённый мост `browser_tool`, изоляция вкладок браузера по workspace, и исправление #824 для basic-auth

Возможности Удалённый browser_tool мостится в локальный браузер пользователя (Electron) — Агенты, работающие на удалённом workspace (безголовый сервер, docker, WebUI), теперь могут управлять…

Возможности

  • Удалённый browser_tool мостится в локальный браузер пользователя (Electron) — Агенты, работающие на удалённом workspace (безголовый сервер, docker, WebUI), теперь могут управлять локальным настольным BrowserPaneManager пользователя от начала до конца. Добавлена возможность client:browser:invoke через WebSocket (объявляется при рукопожатии, сервер вызывает клиент, ошибка Error с .code сохраняется в обоих направлениях), зеркашущая работу shell.openExternal для OPEN_URL. Транспорт получает hasClientCapability / findClientsWithCapability для маршрутизации; Electron получает новый диспетчер IPC __browser:invoke с авторизацией по ключу владельца для каждого метода, отключением повторного использования окон для удалённых вызывающих, listInstances, ограниченными сессиями, и преобразованием BufferUint8Array для скриншотов по проволоке. server-core получает RemoteBrowserPaneManager (реализация IBPM, привязанная к сессии) и SessionManager.getBrowserPaneManagerForSession с осведомлённой о возможностях клиентской fallback и очисткой привязанного к сессии на отключение. uploadFile блокируется через мост; evaluate ограничен локальной настройкой allowRemoteEvaluate. Среда выполнения Pi получает дружелюбные отображения ошибок для BROWSER_NO_CAPABLE_CLIENT, CAPABILITY_UNAVAILABLE, CLIENT_DISCONNECTED, CLIENT_REQUEST_TIMEOUT, BROWSER_INSTANCE_NOT_OWNED, BROWSER_REMOTE_UPLOAD_NOT_SUPPORTED, и BROWSER_REMOTE_EVALUATE_BLOCKED, и теперь зеркашивает getBrowserToolEnabled из Claude, чтобы Pi больше не рекламировал browser_tool, когда переключатель выключен. 27 новых тестов покрывают упаковку по проволоке, авторизацию по методам, интроспекцию возможностей, fallback между хостом и клиентом, скриншоты, сохранение кодов ошибок и контракт отображения ошибок Pi. (1d926c33)

  • Вкладки браузера изолированы по workspaceBrowserPaneManager глобален в процессе, и STATE_CHANGED ранее транслировался { to: 'all' }, поэтому чат в workspace A видел вкладки и баннеры статуса, принадлежащие сессиям из workspace B. Теперь каждый BrowserInstance (и DTO BrowserInstanceInfo) несёт nullable workspaceId; STATE_CHANGED маршрутизируется на { to: 'workspace', workspaceId }, когда установлен (с fallback на { to: 'all' } для несвязанных ручных окон); обработчик browserPane.LIST фильтрует по ctx.workspaceId; рендерер читает новую browserInstancesForWorkspaceAtomFamily, ключированную по activeWorkspaceId. Окна всё ещё работают параллельно как настоящие BrowserWindow — это фильтр видимости UI, а не песочница. REMOVED / INTERACTED остаются трансляциями во все (только id, безопасные no-op на workspace, которые никогда не видели запись). workspaceId отправляется как опциональное на DTO, поэтому старые рендереры терпимо обращаются с отсутствующими значениями (считает undefined как null → проходит фильтр, эквивалентно поведению сегодня). 17 новых тестов покрывают фильтр атома, маркировку BPM и маршрутизацию трансляций/LIST. (af817192)

Улучшения

  • Блок markdown-preview задокументирован в справочнике rich-output — Блок markdown-preview (выпущен в v0.9.6) теперь описан в apps/online-docs/go-further/rich-output.mdx рядом с существующими записями html-preview / pdf-preview / image-preview, чтобы пользователи, находящие блок в чате, могли найти примеры использования и справочник полей src / items в документации. (2d9693b1, 70c2955f)

  • Мост браузера наблюдается из логов сервера — Три строки sessionLog.info в SessionManager теперь подтверждают, выполнился ли setRpcServer при загрузке и выполнился ли блок пересылки браузерной панели при инициализации агента. Позволяет диагностировать удалённые workspace, которые всё ещё сталкиваются с "Browser window controls are not available", только по логам сервера, без подключения отладчика. (ad26e61d)

Исправления

  • Рендерер принимает как локальные, так и удалённые идентификаторы workspace при фильтрации вкладок — При подключении к удалённому workspace рендерер имеет два важных идентификатора: activeWorkspaceId (локальная идентичность окна AIKraft Agents, используется для локально открытых ручных вкладок) и activeWorkspace.remoteServer.remoteWorkspaceId (идентификатор удалённого сервера, используется удалённым агентом при маркировке вкладок через мост WS). Первая версия фильтра изоляции по workspace соответствовала только activeWorkspaceId, поэтому вкладки, помеченные удалённым id (что несёт каждая удалённая агент-открытая вкладка), были отфильтрованы — полоска вкладок TopBar и значок статуса в панели инструментов стали невидимыми для удалённых браузеров, а их скрытие делало их недоступными. Заменён atomFamily на простую функцию filterInstancesForWorkspace(local, remote): вкладка соответствует, если любой id совпадает (или если workspaceId равен null/undefined для обратной совместимости). (bf8429fa)

  • STATE_CHANGED снова транслируется всем клиентам; фильтр видимости живёт в рендерере — Фильтр на стороне сервера (на STATE_CHANGED и обработчике LIST) был неверным для зеркальных удалённых workspace: транспортный workspaceId рендерера — это локальная идентичность окна, тогда как удалённо-замкнутые вкладки браузера носят идентификатор удалённого сервера. Они никогда не совпадают, поэтому STATE_CHANGED, направленный на удалённый id, отбрасывался слоем маршрутизации WS (никакой локальный рендерер не заявляет себя как принадлежащий удалённому workspace), и LIST возвращал пустой результат по той же причине. Изоляция по workspace теперь живёт полностью в рендерере (filterInstancesForWorkspace), который знает оба id через activeWorkspace.remoteServer.remoteWorkspaceId. Обработчик возвращается к трансляциям { to: 'all' } и полному ответу LIST. Конфиденциальность не изменена — каждый локально подключённый рендерер принадлежит одному пользователю — и удалённые вкладки наконец-то отображаются в TopBar workspace, которому они принадлежат. (f831bb42)

  • Нет повторного использования окон на пути жизненного цикла удалённого моста (закрывает межрабочие перехваты) — Диспетчер возможностей корректно устанавливал allowReuseManual=false для createForSession, но getOrCreateForSession и focusBoundForSession шли через публичные помощники, которые по умолчанию используют allowReuseManual=true. browser_tool open удалённого агента маппится на focusBoundForSession, поэтому он мог принять несвязанное окно, оставленное локальной сессией — именно межрабочий перехват, который фильтр workspaceId должен был блокировать. Фильтр workspaceId всё ещё помогает, но это best-effort: окна, созданные до маркировки (или через пути, которые не устанавливают workspaceId), имеют workspaceId=null и остаются универсально пригодными для перехвата. Ремень и браслет: каждый удалённый вызов жизненного цикла теперь передаёт allowReuseManual=false, поэтому удалённые сессии всегда создают новые окна, если они ещё не владеют одним. (ce3340a1)

  • Повторное использование несвязанных окон ограничено принадлежащим workspace (больше нет "вкладка переехала из workspace A в B") — Когда сессия завершается, unbindAllForSession очищает boundSessionId и меняет ownerType на 'manual', чтобы следующий ход той же сессии мог повторно привязать окно; workspaceId, помеченный при создании, сохраняется. findReusableUnboundInstance соответствовал ЛЮБОМУ несвязанному окну 'manual' независимо от workspace, поэтому сессия в workspace B с радостью подхватывала оставшееся окно из workspace A — bindSession затем перезаписывал workspaceId на B, фактически "перемещая" окно из A в B и делая полосу вкладок workspace A потерявшей запись, тогда как B приобретала её. Повторное использование теперь разрешено только если workspaceId кандидата равен null (настоящее пользовательское ручное окно — пригодное для кого угодно) или совпадает с workspaceId вызывающего. Повторное использование на следующем ходу в том же workspace всё ещё работает. (ceb24603)

  • Ручные окна, открытые через TopBar, наследуют workspace, в котором они были открыты — Обработчик browserPane.CREATE создавал ручные окна с workspaceId, по умолчанию равным null, что фильтр изоляции по workspace намеренно трактует как "видимое для всех workspace" — поэтому вкладка, открытая через TopBar, просачивалась в полосу вкладок каждого workspace. Рендерер, который инициирует CREATE, всегда имеет ctx.workspaceId установленный, поэтому обработчик теперь передаёт его в createInstance / createForSession. Вызывающие из CLI / агент-харнесса без контекста workspace (ctx.workspaceId === null) всё ещё получают поведение трансляции во все как безопасный fallback. (7dfcaeac)

  • BrowserInstance проецируется в простой снимок перед возвратом через IPC — Локальный BrowserPaneManager.getInstance(id) возвращает живой BrowserInstance, который встраивает нативные ссылки Electron (window: BrowserWindow, pageView: BrowserView, ...). Когда диспетчер __browser:invoke возвращал этот объект через IPC, сериализатор структурированного клонирования Electron выбрасывал An object could not be cloned, и вызов getInstanceAsync на стороне удалённого агента, используемый для логирования, завершался ошибкой. Добавлен toSnapshot(instance), который выдаёт только поля, объявленные в IBPM (ownerType, ownerSessionId, isVisible, title, currentUrl), и маршрут диспетчера для ветки getInstance перенаправлен через него. (8e2534b5)

  • source_test кодирует basic-auth учётные данные в base64testApiConnectionWithAuth интерполировал сырое значение из хранилища в заголовок Authorization, поэтому basic-auth источники получали Basic {"username":"...","password":"..."} и получали 401 от каждого провайдера. Хранилище сохраняет учётные данные source_basic как JSON (записанные source_credential_prompt / WebUI); путь выполнения в api-tools.ts buildHeaders уже разбирает и кодирует в base64 — путь валидатора был просто забыт. Теперь разбирает токен как JSON, если он содержит username+password; сохраняет поведение pass-through для унаследованных / вручную отредактированных base64-записей и любой не-JSON-строки, зеркашивая buildHeaders. Три регрессионных теста покрывают JSON-форму, унаследованную уже-кодированную форму и не-JSON мусорный токен. Исправляет . (96dd7c0d)

Несовместимые изменения

  • Нет. Поле workspaceId на DTO BrowserInstanceInfo опционально, поэтому более старые рендереры и агенты терпимо обращаются с отсутствующими значениями (считает undefined как null → проходит фильтр видимости, эквивалентно поведению до версии 0.10.0).

Дополнительно в этом выпуске

  • Добавлена панель мониторинга рабочих процессов Hatchet для удобного отслеживания задач

← Все версии