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 в langLabel MarkdownRendererlang из 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.svelteAbortController в 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) отложена — текущее решение устраняет главный симптом.