Исправления
-
OAuth токены молча обновляются перед построением агента — В холодных сессиях
SessionManager.sendMessageвыполнялgetOrCreateAgent→buildServersFromSources→refreshOAuthTokensIfNeededв этом порядке. Первичная сборка видела устаревшие токены, генерировалаAUTH_REQUIRED, и обёртка вызывалаmarkSourceNeedsReauth— устанавливаяisAuthenticated=falseна диске. Пользователь видел краткий мерцание "требуется авторизация" перед поздним обновлением состояния. Три согласованных изменения: (1) обновление теперь запускается доgetOrCreateAgent, чтобы внутренняя сборка холодной сессии видела свежие токены, старыйrefreshOAuthTokensIfNeeded(обновление + условная пересборка) удалён, и единственная сборка после обновления — это единственная сборка за отправку; (2)buildServersFromSourcesпереклассифицируетAUTH_REQUIRED→TOKEN_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 не пострадал, так как использует опцию SDKappend:, которую Claude SDK добавляет дословно. Новый помощникapplySystemPromptOverrideнаносит все три приватные поля SDK (state.systemPrompt,_baseSystemPrompt,_rebuildSystemPrompt), чтобы запрос выжил как при персональных сбросах, так и при перестройках из-за изменений инструментов. Шаблон соответствуетapplySystemPromptOverrideToSessionOpenClaw — тот же 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, а не ошибкой завершения процесса); теперь она различает причиныbinaryvscwdvsunknown, выдаёт типизированные ошибкиsdk_binary_missing/sdk_cwd_missingпосле исчерпания попыток повтора, и сохраняет 2-секундную автоматическую повторную попытку для окна автообновления пакета. Вспомогательные функции вынесены вagent/spawn-helpers.tsдля модульного тестирования (34 новых теста; существующий набор ветвлений расширен о покрытие v2 коллбэков). (12166f4a,e40a3801) -
source_testlastTestedAtтеперь сохраняется — интерфейс больше не всегда показывает "Никогда" — Обработчик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 … при первом чате в импортированной сессии, теперь успешно продолжают работу без ошибок — защита перенаправляет через существующий механизм восстановления ветки и сохраняет чистые метаданные ветки для следующего запуска.