1.0.11

Fixed (#371)

  • Терминальные error-тексты хода персистятся несогласованно (#371, найдено при аудите #369): таймаут хода писал role='assistant'-строку с эхом message_persisted, а run error / no provider / отказ resume / resume run error — нет; в live клиент материализовал оптимистичный error-бабл, который до перезагрузки висел навсегда «неподтверждённым» (merge не находил серверную строку), а после F5 исчезал — расхождение live/reload. Продуктовое решение — паритет с таймаутом: всё, что показано в live, сохраняется в истории. Новый helper persistAssistantTextWithEcho (bounded fresh-ctx 10с + addMessageWithRetry + эхо assistantPersistedEchoMessage на WS-канале, best-effort: неудача записи логируется и не подавляет терминальное событие) подключён в пяти ветках: no provider («Ошибка: у агента не настроен LLM-провайдер…»), nil result («Агент не смог обработать запрос»), run error («Ошибка: …»), предраннеровский отказ resume («Не удалось возобновить ход») и resume run error; persistTurnTimeout отрефакторен на тот же helper (поведение не изменилось). В resume-ветке с пустым FinalContent контент синтезируется из ошибки («Ошибка: …», паритет с run_error-веткой основного хода) — раньше клиент получал пустой text и показывал безликое «Ошибка агента»; непустой частичный ответ показывается и персистится как есть. Фронтенд без изменений кода: свап id оптимистичного бабла по assistant-эху + rule-2 merge уже покрывают поток (добавлен TS-тест на error-лэйн). Осознанные trade-offs (из ocr review): паритет с reload — текстовый (wire-флаги _error/_turn_end в ChatMessage не сохраняются: metadata-колонки нет, IsError зарезервирован за role='tool'; после перезагрузки строка читается как обычный assistant-текст); персист синхронный — порядок «эхо раньше терминального события» это контракт #369/#371, на здоровой БД запись занимает миллисекунды; канал строки = канал терминального события (история агента не фильтруется по каналу: GetRecentUIMessages/GetRecentDialogMessages выбирают по agent_id, live-строки раннер вообще пишет без канала); err.Error() в тексте — не регрессия: тот же текст и раньше уходил пользователю в live-событии. Тесты: Go — helper (WS: строка+эхо с id строки; email: строка без эха), интеграционно no provider / run error / resume fail / resume run error (строка + эхо + _error на терминале; синтез контента при пустом FinalContent), best-effort при полном отказе БД (терминальное событие публикуется, эха нет); TS — подтверждение error-бабла assistant-эхом по контенту.