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

95 lines
6.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Живой прогон: что нашли и починили (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` из чата (и подсказка обновлена).
## Как повторить
```bash
# 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'
```