[2026-06-04] taiga-vault: personal/projects/nolvu-reflect-skip.md
This commit is contained in:
@@ -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
|
||||
|
||||
---
|
||||
|
||||
## Связанные заметки
|
||||
|
||||
|
||||
Reference in New Issue
Block a user