diff --git a/personal/projects/nolvu-reflect-skip.md b/personal/projects/nolvu-reflect-skip.md index bf28337a..6f340f10 100644 --- a/personal/projects/nolvu-reflect-skip.md +++ b/personal/projects/nolvu-reflect-skip.md @@ -13,7 +13,7 @@ tags: - psychology - skip-tools created: '2026-05-17' -updated: '2026-05-28' +updated: '2026-06-04' related: - "[[personal/projects/psychologist-app/overview]]" - "[[personal/projects/psychologist-app/dev-setup]]" @@ -25,7 +25,7 @@ related: - Активная кодовая база: `~/Developer/reflect-skip/` - Android: публикуется на `https://reflect.qentra.top` (F-Droid) - iOS: собирается через `Project.xcworkspace` → `Darwin/ReflectApp.xcodeproj` -- Текущая версия: v0.0.92 (Android APK), v0.0.25 (iOS) +- Текущая версия: **v0.0.108** (ветка `feature/document-import`; `main` на v0.0.60, не слит) ## Структура проекта @@ -43,14 +43,28 @@ related: - LaunchAgent: `ai.reflect-proxy` → `localhost:8765` - Cloudflare tunnel: `ai.qentra.top` → `localhost:8765` -- Бэкенд: `claude-proxy` на `localhost:3456` +- Бэкенд: `claude-code` (переключено с `claude-proxy`) - Лог сессий: `~/Developer/reflect-skip/sessions.log` ## Агенты -- **NarratorBot** — нейтрализует ввод пользователя (убирает эмоциональную окраску, 3-е лицо) -- **AnalystBot** — задаёт 2-3 глубоких вопроса по профилю + контексту сессии -- **SessionManager** — управляет историей сессий +- **NarratorBot** — нейтрализует ввод пользователя (убирает эмоциональную окраску, 3-е лицо); детектирует `[SCHEDULE:]` тег для schedule intent +- **AnalystBot** — задаёт 2-3 глубоких вопроса по профилю + контексту сессии; возвращает `AnalystJSON` с полями `sessionComplete`, `moodDelta`, `suggestedInsight` +- **SafetyClassifier** — Layer 2, параллельный crisis detection; роутинг off-topic → redirect card +- **SessionManager** — управляет историей сессий, пейсингом (turn count, fatigue, hard limit 8 оборотов), closing-флоу + +### ProcessingResult (текущие кейсы) +```swift +enum ProcessingResult { + case questions([String], observation: String?) + case readyToClose([String]?, observation: String?) + case offTopic(String) + case scheduleIntent(String) + case crisis(CrisisDetectionResult) + case moodDeclining / moodImproving // через OrbState + case error(String) +} +``` ## Исправления (v0.0.92, 28 мая 2026) — Android vault validation @@ -65,11 +79,17 @@ related: **Исправление:** заменили на `@AppStorage("_vaultErr")` и `@AppStorage("_vaultFound")` — идут через SharedPreferences → всегда триггерят Compose recompose. **Файл:** `Sources/naisei/Views/OnboardingView.swift` -### Открытые проблемы (v0.0.92) -- ❌ На главном экране "0 сессий", "0 дней" -- ❌ Профиль и Инсайты пустые -- ❌ Агент при начале сессии говорит "нет данных" (не видит vault) -- **Гипотеза:** vault URI сохраняется, но ProfileManager/SessionManager читают данные не через SAF URI, а по прямому пути — на Android без SAF доступа путь недоступен +## Реализовано (v0.0.92 → v0.0.108) + +- **Session pacing + fatigue + recovery** — turn counter, fatigue detection, session recovery флоу +- **Structured session close** — `generateClose()`, `applyOps()`, ProfileManager DI; X-кнопка вызывает `endSession()` +- **Completion routing + insights** — `readyToClose` в ProcessingResult, `insights.md`, agentic insight +- **One-card UX** — `isActive`, `appendCard`, growing TextEditor, history view +- **Schedule intent detection** — Narrator `[SCHEDULE:]` тег → `ProcessingResult.scheduleIntent` +- **Mood delta → Orb visual feedback** — `MoodDelta` enum, `AnalystJSON`, `OrbState.moodDeclining/moodImproving` +- **Warm track** — реальные даты сессий из file attributes, `lastSessionText`, `daysSinceLastSession` → Analyst context +- **LLM safety classifier + off-topic routing** — `SafetyClassifier` Layer 2, параллельный crisis detection, redirect card +- **Android voice bridge** — `SpeechRecognizer` bridge via `AndroidBridge`, Kotlin companion в `AndroidAppMain` ## Исправления (v0.0.25, май 2026) @@ -103,85 +123,6 @@ Vault путь для импорта: `personal/psychology/` (на Taiga). - Если на iOS-симуляторе не появляется `DevSetupView` (ввод токена) — значит `dev_setup_done = true` в UserDefaults. Удалить приложение с симулятора и запустить заново. - `ReflectApp/` в корне — legacy, не трогать. Реальные правки только в `Sources/ReflectApp/`. -## Plan: Session Pacing & Closing (не реализовано) - -### Проблема - -`SessionViewModel.endSession()` существует и реализует полный closing-флоу (инсайт → warm phrase → oneLastThing → vault save), но **нигде не вызывается из UI**. -`dismissSession()` (кнопка X) отменяет сессию без сохранения. -Пейсинга нет: агент не знает сколько оборотов прошло, нет правил завершения в systemPrompt, нет сигнала `session_complete` в `AnalystJSON`. - -### Решение: Вариант Б + hard limit - -**Шаг 1 — Quick-fix: X-кнопка вызывает `endSession()` (даёт рабочий closing уже сейчас)** - -Вместо `dismissSession()` при нажатии X — вызывать `endSession()`. -Пользователь сам решает когда нажать, но closing-флоу отработает корректно. - -**Шаг 2 — Turn count в SessionManager** - -Добавить `var userTurnCount: Int = 0`, инкрементировать в `processUserInput()`. -Передавать в `AnalystBot.analyze()` как параметр `turnCount`. - -**Шаг 3 — `session_complete` в AnalystJSON** - -```swift -struct AnalystJSON: Codable { - let questions: [String] - let observation: String? - let crisisSignal: Bool - let suggestedInsight: String? - let sessionComplete: Bool // новое поле -} -``` - -**Шаг 4 — Пейсинг-правила в systemPrompt AnalystBot** - -``` -Правила ведения сессии (параметр turn_count передаётся в каждом запросе): -- После 4+ оборотов: если пользователь пришёл к пониманию, тема исчерпана - или повторяется — установи session_complete: true -- Если открылась новая важная тема — продолжай (session_complete: false) -- После 7+ оборотов: session_complete: true независимо от темы -- session_complete: true — предложение закрыть, не принудительное завершение -``` - -**Шаг 5 — `.readyToClose` в ProcessingResult** - -```swift -enum ProcessingResult { - case questions([String], observation: String?) - case readyToClose([String]?, observation: String?) - case crisis(CrisisDetectionResult) - case error(String) -} -``` - -SessionManager возвращает `.readyToClose` когда `analystResponse.sessionComplete == true` ИЛИ `userTurnCount >= 8`. - -**Шаг 6 — UI карточка в SessionViewModel** - -При `.readyToClose`: показать вопросы (если есть) → после ответа добавить карточку -"Кажется, мы хорошо прошлись по теме. Завершить сессию?" -Кнопки: "Завершить" → `endSession()` | "Продолжить" → сбросить сигнал на текущий turn. - -**Шаг 7 — Интеграционные тесты (TDD: писать до реализации шагов 2-6)** - -- `testAnalystReturnsSessionComplete_afterExhaustedTopic()` -- `testSessionManager_readyToClose_onSessionCompleteSignal()` -- `testSessionManager_hardLimit_at8Turns()` -- `testSessionViewModel_showsCompletionCard_onReadyToClose()` -- `testSessionViewModel_continueAfterReadyToClose_resetsSignal()` - -### Порядок реализации - -1. Тесты (шаг 7) — все RED -2. Quick-fix X-кнопки (шаг 1) — немедленно даёт рабочий closing -3. Шаги 2-5 — данные + логика -4. Шаг 6 — UI карточка -5. Все тесты GREEN - ---- ## Связанные заметки