0.134.13
Fixed
Реализованы все 10 P0-задач из аудита docs/audit/web-2026-06-06.md (исключая W-002, W-003, W-013 — помечены «НЕ БАГ»).
- W-001, W-006: Knowledge Graph: прогресс-бар ребилда никогда не появлялся — два связанных дефекта: (1)
kgProgress.handleKGProgressEventпарсилagentIdизdata.agent_name(строка-имя), и из-за опечатки в бекендеagent_idупаковывался в это поле черезfmt.Sprintf("%d", ...), в результатеkgProgress.agentIdвсегда был0; (2) модульный$stateвkgProgress.svelte.tsне был реактивен через геттер-функциюgetKGProgress(), поэтому$derived(getKGProgress())не подписывался на изменения. Исправлено: (a)internal/channels/websocket.go— добавлено полеAgentID int64 \json:“agent_id,omitempty”`вWSOutboundEvent, вkg_progresseventagent_idпередаётся числом в новом поле; (b)kgProgress.svelte.tsпереписан наclass KGProgressStoreсcurrent = $state(…), вroutes/agents/[id]/kg/+page.svelteимпорт переключён наkgProgressStore.current`. Теперь при ручном ребилде KG прогресс-бар появляется и корректно заполняется. - W-004: XSS в
langLabelMarkdownRenderer —langиз code-блока markdown подставлялся в HTML-строку без эскейпа. LLM мог отдать"><img src=x onerror=alert(1)>и вставить скрипт (DOMPurify спасал, но defense-in-depth был слабым). Добавлены helper’ы:safeLang()валидируетlangпо whitelist/^[a-zA-Z0-9_+-]+$/,escapeAttr()эскейпит опасные символы. Потенциально опасныйlangтеперь отбрасывается до попадания в DOM. - W-005: Soul: неверный текст тоста после
clearEvolved— после очистки авто-выученной личности показывалось «Душа агента сохранена», что вводило в заблуждение (никакого сохранения не было). Заменено на «Авто-выученная личность очищена». - W-007: Toast: setTimeout не отменялся при ручном dismiss — при ручном закрытии тоста таймер продолжал жить и через
timeoutмс повторно вызывалdismissдля уже удалённого тоста. В режиме спама (100 ошибок) накапливались фантомные таймеры с утечкой памяти. ВToastдобавлено полеtimerId, вdismiss()—clearTimeout(t.timerId). Дополнительно добавленdismissAll(). - W-008: AvatarCrop: body.overflow=‘hidden’ залипал при unmount — если компонент размонтировался в состоянии
open=true, скролл страницы блокировался.$effectпереписан с cleanup-функцией, восстанавливающей предыдущее значение (а не сбрасывающей в''), что корректно работает и при вложенных модалках. - W-009: FavoritesPanel: двойной обработчик Escape — одновременно работали
document.addEventListener('keydown', handleKeydown)вonMountи<svelte:window onkeydown>, обработчик вызывался дважды. УдалёнaddEventListener/removeEventListener, остался только<svelte:window>. - W-010: switchAgent race condition — при быстром переключении 3+ агентов подряд предыдущий
loadMessagesмог догрузиться и перезаписать state более нового агента. Реализовано системно:FetchOptionsвlib/api/client.tsрасширено полемsignal?: AbortSignal(пробрасывается вfetch),api.agentMessagesиapi.agentModels.listпринимаютopts.signal. Вroutes/chat/+page.svelte—AbortControllerвswitchAgentс проверкойsignal.abortedпосле каждогоawait, cleanup вonMount. - W-011: ChatMessage: локальные
reaction/favoriteне синхронизировались сmsg— при обновленииmsgот бекенда (черезresyncMessages) локальный$stateоставался со старым значением, UI показывал устаревшее. Добавлен$effect, синхронизирующий state сmsg.reaction/msg.favorite. - W-012: реакции/избранное не подтягивались с сервера —
initReactions()/initFavorites()существовали, но никем не вызывались,Map-кеш жил сессию и расходился с реальностью. ВChatStore.handleWSEvent('turn_end')послеapi.agentMessagesстроятсяRecord<id, Reaction>иRecord<id, boolean>и инициализируют кеши. Полная миграция на$state-class (W-023, P1) отложена — текущее решение устраняет главный симптом.