1.7.9
Fixed (#408)
- Вызовы инструментов во время live-хода больше не появляются выше только что отправленного сообщения пользователя.
sortMessagesForRenderставил весь server-id блок перед optimistic-хвостом: пока user-строка не получила server id (echomessage_persistedещё в пути), карточкаtool_batch_endс реальным id рендерилась выше неё; окно расширялось тем, что idle-актор персистил user-строку вhandleInboundбез echo (его публиковал только busy-путьpersistUserMessageOnReceive), аensureUserMessageSyncedранний return, когда optimistic user — хвост проекции. Исправление двумя осями. (1) Сервер:routeToActorвыделяет per-ходовойturn_id(UUID,bus.MetaKeyTurnID) ДО персиста user-строки — его несут echo,thinking, все кадрыstreamHook(штамп вpublish()), терминальные_turn_endиagent_statusresume; idle-путьhandleInboundпубликуетmessage_persistedecho для WS-строк сclient_temp_id(зеркало busy-пути, паттерн #370) — оптимистичная user-строка свапается на server id сразу, а не после turn_end-merge. (2) Клиент: optimistic-строки получаютsort_anchor= max server id на момент append — строка держит место выше server-строк, созданных позже в том же ходе (id монотонны), echo-свап не двигает позицию; строки без anchor (легаси/фикстуры) остаются хвостом — обратно совместимо, включая legacytool_batch_endбезmessage_id.
Fixed (#414)
- Индикатор «печатает…» больше не остаётся в чате после финального ответа агента. Сброс busy-кластера был размазан по обработчикам с разными наборами полей:
message_persistedиresetConnectionState(WS close) не сбрасывалиisThinkingвовсе, дубликаты/запоздалые терминалы предыдущего хода не отфильтровывались (нет корреляции), аsendAgentStatusResumeмолчал приbusy=false— переподключившийся клиент не получал авторитетного idle и висел с устаревшим индикатором до нового сообщения/F5. Клиент получил единый идемпотентный терминальный переходfinalizeLiveTurn(waitingForAgent, isThinking, agentPhase, стрим-буферы, active tools, skill runs, stop-frame, error-баннер;pendingAskпереживает терминал — вопрос живёт до ответа), в который сходятся все подтверждённые терминальные пути (turn_end, terminalmessage, непустойstream_end,agent_status busy=false,ask_user, unbounderror, hard-watchdog, WS close). Guard от устаревших событий:activeTurnIdпринимается (one-shot) от «ход жив»-кадров (thinking/delta/user-echo/resume) и сравнивается сturn_idтерминала — чужой/дубль терминал отбрасывается целиком;enterAwaitingTurnиresetStateсбрасывают корреляцию и dedup-окноfinalizedTexts. Сервер шлётagent_statusпри каждом connect/attach ВСЕГДА (busy=true со снапшотом + turn_id, либо busy=false) — reconnect гарантированно приводит индикатор и композер в idle. Повторные терминалы одного хода (легитимные пары stream_end→turn_end, ask_user→turn_end) остаются рабочими: turn_end несёт свои токены/persist_failed/refetch вне finalize.
Review (#408, #414)
- ocr review — исправлены:
OnToolProgressпубликовал в обходstreamHook.publish(единственный несштампованный turn-scoped кадр), гонкаcurrentTurnIDсо снапшотом reconnect (штамп доbusy.Store(true)— GetSessionState гейтится по busy),finalizeLiveTurnне чистилthis.error(баннер переживал ход на живом сокете),enterAwaitingTurn/resetStateне сбрасывалиfinalizedTexts/activeTurnId,adoptTurnIdперезаписывал корреляцию (one-shot-принятие — чужой thinking не может угнать id живого хода),wsCoalescablePair— ключ слияния delta-кадров дополненTurnID(слабая уникальность stream_id допускает коллизию между ходами) + точечный тест. Отклонены с обоснованием: one-shot latch на повторные терминалы (сломал бы обязательный turn_end после stream_end — токены/persist_failed/refetch), мемоизацияmaxKnownServerId(окно чата ограничено,$derived-сорт уже O(n log n) на мутацию — кэш добавил бы пути инвалидации), перенос очистки subagent/extraction-остатка в finalize (memory-extraction сознательно живёт за границей хода), числовой ordinal вместо стабильной сортировки (стабильностьArray.prototype.sortгарантирована ES2019+, прежний компаратор полагался на тот же контракт). Тесты: Go — turn id на эхе/в очереди актора/терминале/кадрах хука, idle-echo с tempID и без, always-sendagent_status(idle/busy+turn_id), маппинг_turn_id→wire, коалесценция чужих ходов; vitest — live-порядок optimistic user против server-id tool-карточек (одиночный/несколько batch’ей/legacy/REST), задержанный echo без перестановки ленты, полная последовательностьthinking → tool_batch_end → message_persisted → stream_end → turn_endс совпадением live/reload-порядка, терминальные переходы по каждому событию и их перестановкам/дублям, stale-терминал чужого хода, WS close/reconnect-resume, ask_user-исключение. Проверки:go test ./... -count=1(0 падений),golangci-lint(0 новых; легаси-SA5011 в нетронутых файлах),go vet,npm run check(0),npm test(909),make build-cross(6 платформ) — зелёные.