Files
subochev 2c71542b13 Десктоп-клиент: Compose-UI, сессии, SQLite-кэш, голос, живые тесты
- settings/: settings.json (агенты, тема, clientId), атомарная запись
- persistence/: ConversationMetaRepository — группы, lastSeen, превью,
  watermark синхронизации (synced_at через PRAGMA table_info)
- session/: AgentRegistry (параллельное подключение, деградация в офлайн),
  AgentConnection (журнал + мета + живой список диалогов conversationsFlow,
  refresh по SSE-потоку), ChatSession (history + backfill + live + preview,
  дедуп по id, 1 мс шаг watermark, syncMutex), LiveTurn
- UI: рейка агентов, список диалогов (живое обновление), чат со стримингом,
  композер (Enter отправляет, Shift+Enter переносит, Ctrl+M — голос),
  окна «Агенты»/«Группы»/«Новый диалог», тёмная и светлая темы
- voice/: mic-api → Silero VAD → Vosk на одном выделенном потоке;
  VoiceNative.warmUp() до skia (иначе SIGSEGV), модель в артефакте
- markdown/: вендоренный рендер из ai/assistent
- ошибки в баннер через AppState.reportError (CancellationException игнорируется)
- тесты 103 (реальная SQLite + фейковый транспорт); против живого агента —
  RealServerIT, RealAppStateIT, RealSpeechIT
- LIVE-RUN-NOTES.md: живой прогон в GUI и найденные/починенные баги
2026-09-28 10:08:10 +03:00

6.1 KiB
Raw Permalink Blame History

Живой прогон: что нашли и починили (28.09.2026)

Первый прогон десктопа не на фейках, а на живом :standalone-агенте (Qwen через vLLM на 192.168.88.135) с настоящим GUI: окно Compose, клики/печать через ydotool, проверка скриншотов.

Что проверено вживую

Шаг Результат
Приложение подключается к агенту шапка: «Локальный агент» + зелёное «на связи»
Список диалогов подтягивается с сервера (33 диалога), поиск/время/аватары на месте
«+» (новый диалог) сервер вернул новый диалог, счётчик на сервере 33 → 34
Отправка сообщения текст ушёл, ответ LLM пришёл в UI, в истории обе реплики
Голос (Ctrl+M) «идёт запись» (микрофон красный) → повторное нажатие → «Речь не распознана», без падения JVM

Ход агента при этом длинный: ответ занял 0.5 с, а один разбор с длинным промптом — 144 с; это важно для бага №1 ниже.

Починенные баги

1. :client: POST /messages умирал по таймауту 15 с (upstream)

Сервер отвечает на POST /conversations/{id}/messages только по завершении всего хода агента (LLM + тулы), а это минуты. Клиент шёл с дефолтным request-timeout Ktor → любая длинная реплика обрывалась на 15-й секунде (RealServerIT показал реальные 144 с).

Фикс в agentik/client/.../ConversationClient.kt: у запроса отправки снят таймаут чтения (noReadTimeout(), тот же приём, что для SSE). Регрессионный тест — в RealServerIT.

2. Конфликт skiko и нативных ASR-библиотек → SIGSEGV

Найдено при добавлении тестов голоса: если onnxruntime/Vosk инициализируются после skia, JVM падает в C++-логах. В проде это был бы краш при первом нажатии микрофона (окно-то уже нарисовано). Подробности и таблица порядка загрузки — в MIC-ASR-SEARCH.md §8. Фикс: VoiceNative.warmUp() в main() до application {}, плюс forkEvery = 1 в тестах.

3. Список диалогов не обновлялся никогда

AgentConnection.listConversations() читал agent.conversationStore.list(...) один раз — а встроенный кэш либы наполняется асинхронно (listFlow), так что даже «один раз» приходилось на пустой кэш. Итог: сервер отдаёт 33 диалога, а в UI список пуст, и обновить его было нечем.

Фикс: AgentConnection.conversationsFlow — опрос встроенного кэша с warmup-циклом на старте + подписка на SSE-поток (outbox.agentEvents) для появления/удаления диалогов, созданных другими клиентами, + явный refresh() после своих create/delete/rename.

4. Enter не отправлял сообщение, хотя подсказка это обещала

Поле многострочное, Enter вставлял перевод строки. Фикс: onPreviewKeyEvent в композере — Enter отправляет, Shift+Enter переносит строку (тогда подсказка «отправка Enter» становится правдой).

5. Клик по нижней/верхней части поля ввода не фокусировал его

BasicTextField высотой в строку внутри Box с паддингами: клик мимо самой строки текста фокуса не давал. Фикс: клик по всему боксу запрашивает фокус (clickable + focusRequester).

6. Ложный красный баннер «StandaloneCoroutine was cancelled»

refresh() отменяет предыдущий job, а runCatching { ... }.onFailure { error = it.message } показывал отмену корутины как ошибку пользователя. Фикс: AppState.reportError() игнорирует CancellationException (все 11 мест переведены на него).

7. Микрофон с клавиатуры

Мышиную кнопку микрофона удобно не всегда, а для проверки голоса без мыши — критично: добавлен Ctrl+M из чата (и подсказка обновлена).

Как повторить

# 1. живой агент (в фоне, из папки проекта)
cd /home/subochev/common-projects/WORK/ai/agentik/standalone
AGENTIK_SKILLS_DIR=./agentik/skills AGENTIK_SYSTEM_PROMPT='Ты полезный ассистент.' \
OPENAI_BASE_URL=http://192.168.88.135:8001/v1 AGENTIK_DB_PATH=./agentik/db.db \
OPENAI_CONTEXT_WINDOW=115000 AGENTIK_MEMORY_DIR=./agentik/memory \
OPENAI_LLM_BACKEND=openai AGENTIK_PORT=8080 OPENAI_API_KEY=... \
OPENAI_MODEL=... AGENTIK_SOUL=./agentik/SOUL.md ./gradlew run

# 2. десктоп (окно)
cd /home/subochev/common-projects/WORK/ai/agentik-desktop
AGENTIK_DESKTOP_HOME=/tmp/opencode/ak-real ./gradlew run

# 3. интеграционные тесты против живого агента
AGENTIK_E2E_URL=http://127.0.0.1:8080/agentik ./gradlew jvmTest \
  --tests '*RealServerIT' --tests '*RealAppStateIT' --tests '*RealSpeechIT'