0.9.2 6 мая 2026 г.

Порядок обновления OAuth, сохранение системного запроса Pi, защита от спауна между машинами, восстановление i18n ключей, исправления source_test/стриминга/браузера

Исправления OAuth токены молча обновляются перед построением агента — В холодных сессиях SessionManager.sendMessage выполнял getOrCreateAgent → buildServersFromSources → refreshOAuthTokensIfNeeded в…

Исправления

  • OAuth токены молча обновляются перед построением агента — В холодных сессиях SessionManager.sendMessage выполнял getOrCreateAgentbuildServersFromSourcesrefreshOAuthTokensIfNeeded в этом порядке. Первичная сборка видела устаревшие токены, генерировала AUTH_REQUIRED, и обёртка вызывала markSourceNeedsReauth — устанавливая isAuthenticated=false на диске. Пользователь видел краткий мерцание "требуется авторизация" перед поздним обновлением состояния. Три согласованных изменения: (1) обновление теперь запускается до getOrCreateAgent, чтобы внутренняя сборка холодной сессии видела свежие токены, старый refreshOAuthTokensIfNeeded (обновление + условная пересборка) удалён, и единственная сборка после обновления — это единственная сборка за отправку; (2) buildServersFromSources переклассифицирует AUTH_REQUIREDTOKEN_EXPIRED, когда учётные данные просто истекли, но могут быть обновлены, и пропускает markSourceNeedsReauth в этом случае (предотвращает мерцание, если обновление пропущено, например, во время периода ожидания); (3) ветви неудач TokenRefreshManager.ensureFreshToken теперь зеркалируют запись на диск source.config в памяти, так что isSourceUsable возвращает false, и источник с ошибкой исключается из intendedSlugs сборкой после обновления. Закрывает . (347820ab)

  • Бэкенд Pi молча удалял системный запрос AIKraft — Pi SDK 0.72.1 session.prompt сбрасывает agent.state.systemPrompt обратно к _baseSystemPrompt на каждом ходу (agent-session.js ~L796), поэтому прямое назначение в pi-agent-server молча удалялось — забирая с собой предпочтения/заметки, <session_state>, <sources>, рабочую папку и все остальное содержимое системного запроса, построенного AIKraft. Бэкенд Anthropic не пострадал, так как использует опцию SDK append:, которую Claude SDK добавляет дословно. Новый помощник applySystemPromptOverride наносит все три приватные поля SDK (state.systemPrompt, _baseSystemPrompt, _rebuildSystemPrompt), чтобы запрос выжил как при персональных сбросах, так и при перестройках из-за изменений инструментов. Шаблон соответствует applySystemPromptOverrideToSession OpenClaw — тот же SDK, то же ограничение, проверенное обходное решение до появления публичного API в upstream. Подключено в оба места, где ранее выполнялось state.systemPrompt = …: обработчик персонального prompt и эфемерную сессию queryLlm. Регрессионный тест проверяет, что все три поля наносятся (ловит исходную регрессию с одним полем и любое будущее удаление одной из записей). Закрывает . (13f6e63e)

  • Защита SDK от спауна с устаревшей рабочей папки из-за импорта между машинами — Ошибка SDK Claude Code native binary not found at … — это вводящая в заблуждение обёртка над spawn ENOENT, которая возникает всякий раз, когда подпроцесс cwd также отсутствует — не только когда бинарник отсутствует. Импорт сессий между машинами сохраняет branchInfo.sdkCwd с исходной машины, поэтому получатель Send to Workspace сталкивается с этой ошибкой при первом чате, даже если пакет в порядке. Теперь перед спауном проверяется устаревший branchFromSdkCwd в chatImpl и перенаправляется через тот же механизм восстановления ветки, который используется в пути после ошибки (сохранение инъекции родительского резюме). Новый коллбэк onBranchForkInvalidated атомарно сохраняет все четыре поля ветки — исправляя существующую ошибку, при которой onSdkSessionIdCleared сохранял только sdkSessionId, оставляя устаревшие поля ветки на диске для перезагрузки при следующем запуске. Классификация ENOENT была вынесена из условия if (isProcessError) (она никогда не срабатывала для обёртки SDK, который является ReferenceError, а не ошибкой завершения процесса); теперь она различает причины binary vs cwd vs unknown, выдаёт типизированные ошибки sdk_binary_missing / sdk_cwd_missing после исчерпания попыток повтора, и сохраняет 2-секундную автоматическую повторную попытку для окна автообновления пакета. Вспомогательные функции вынесены в agent/spawn-helpers.ts для модульного тестирования (34 новых теста; существующий набор ветвлений расширен о покрытие v2 коллбэков). (12166f4a, e40a3801)

  • source_test lastTestedAt теперь сохраняется — интерфейс больше не всегда показывает "Никогда" — Обработчик source_test записывал ISO-строку (new Date.toISOString); Zod-валидатор в validators.ts требует z.number.int.min(0), поэтому строка молча удалялась перед сохранением. Второе скрытое несоответствие усугубляло проблему: session-tools-core/src/types.ts объявлял lastTestedAt?: string, тогда как канонический общий тип уже использовал number. Исправлено переключением обработчика на Date.now и выравниванием локального типа на number. Хранилище, валидатор и интерфейс (formatRelativeTime) уже ожидали метку времени в миллисекундах — никакие другие файлы менять не нужно. Закрывает . (d4427cac)

  • Содержимое ответа ассистента больше не исчезает после завершенияhandleTextComplete неусловно перезаписывал содержимое сообщения, накопленное при стриминге, значением event.text из события завершения SDK. Если event.text приходил пустым (состояние гонки SDK или промежуточное событие без текста), сообщение в пузырьке становилось пустым. Теперь извлекается streaming из состояния и применяется цепочка fallback в обоих путях — обновление существующего и создание нового сообщения: event.text || streaming?.content || existingMsg?.content || ''. Закрывает . (d4427cac)

  • Переключатель браузера теперь полностью отключает секцию системного запроса и правило предпосылки — Три независимых подзадачи, управляемые тем же переключателем getBrowserToolEnabled, который уже был подключён к session-scoped-tools.ts, но нигде больше. (a) Системный запрос: system.ts неусловно вставлял секцию ## Browser Tools и предупреждение "вызовы блокируются, пока вы не прочитаете browser-tools.md" независимо от настройки — теперь ограничено. (b) Слишком широкий matcher предпосылки: prerequisite-manager.ts использовал isBrowserToolNameOrAlias, который совпадал с любым инструментом с именем browser_tool, включая внешние MCP-инструменты, такие как mcp__playwright__browser_tool — сужено до канонических имён сессии ('browser_tool' и 'mcp__session__browser_tool'). (c) Правило всегда регистрировалось: правило регистрировалось даже при отключённой функции, что приводило к неправильному блокированию внешних браузерных инструментов — проверка getBrowserToolEnabled теперь делает правило пассивным, когда переключатель выключен. Закрывает . (d4427cac)

Улучшения

  • Упрощение защиты спауна после рефакторинга по SOLID/KISS — Откат надуманной сложности, выявленной после исправления spawn-ENOENT, при сохранении критически важных частей (предспауновая защита, атомарный onBranchForkInvalidated, корректная классификация ENOENT, регулярное выражение с учётом .app-пакетов). Встроены pickFirstExistingDirectory и probeEnoentPaths; удалён classifyEnoentCause — 4-строчный цикл по директориям и 2-строчная проба не оправдывали абстракцию на единственных точках вызова. Сворачивание трёхзначного binary|cwd|unknown в блоке catch до трёх простых шагов: устаревшая cwd-ветка → восстановление, первая попытка → повтор через 2 с, исчерпание попыток → типизированная ошибка, указывающая на более вероятную причину (удаление 5 отдельных веток enoentCause). Сведение 5-значного объединения reason в recoverFromStaleBranchFork до одного булева isPreSpawn — текст статуса был единственным дифференциатором. Повышение lastResolvedCwd / lastResolvedBinaryPath из полей экземпляра до локальных переменных в chatImpl (видимых внутреннему catch через замыкание) — избегает риска устаревших данных между вызовами chat. ErrorCode централизован в @craft-agent/core/types через повторный экспорт из errors.ts. Чисто −206 строк, без изменения поведения; 608 тестов агента + 102 теста server-core пройдены. (e40a3801)

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

  • Нет. Все исправления обратно совместимы.

Примечания

  • Исправление OAuth меняет порядок операций при каждой отправке сообщения — обновление теперь запускается один раз перед любой сборкой сервера вместо устаревшей сборки. Если вы используете API/MCP источники с пользовательскими потоками OAuth, видимое изменение поведения — меньше записей на диск за цикл обновления и отсутствие мерцания needs_auth на холодных сессиях. Конфигурация или миграция не требуются.
  • Получатели кросс-машинного "Send to Workspace", которые ранее сталкивались с Claude Code native binary not found at … при первом чате в импортированной сессии, теперь успешно продолжают работу без ошибок — защита перенаправляет через существующий механизм восстановления ветки и сохраняет чистые метаданные ветки для следующего запуска.

← Все версии