- 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 и найденные/починенные баги
6.1 KiB
Живой прогон: что нашли и починили (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'