- services/rag: knowledge_docs store — vector search via the shared
embedder (HNSW index, graceful ILIKE fallback), list/delete scoped to
the user, search intentionally global for the single-user deployment
- gateway: GET/POST /knowledge/docs, DELETE /knowledge/docs/:id,
POST /knowledge/search
- plugins/knowledge: knowledge_search and knowledge_ingest tools so the
persona can save and recall information mid-conversation
- main: migrations, store wiring, plugin registration
- llm: Adapter.Vision sends multimodal chat requests (text + image_url,
data URLs supported) and logs the call as purpose 'vision'
- gateway /images/analyze now accepts image_url or image_base64 and
returns real analysis instead of a placeholder message
- main wires the default LLM adapter as the vision analyzer
- httptest coverage for the vision request/response shape
- assistant messages get a 🔊 button that fetches TTS audio and plays
it, toggling to stop
- ChatInput gains a mic button: MediaRecorder capture, base64 upload to
/voice/stt, recognized text fills the input
- api client: voice.tts (blob) and voice.stt helpers
- new emotion package: rule-based sentiment (positive/negative lexicon)
drives mood transitions (开心/平静/难过) with 30min inactivity decay;
affection 0-100 grows with positive interaction, with 初识→熟悉→信赖→
羁绊 tiers
- pipeline records each user message and injects a mood line into the
system prompt so the persona's tone follows her mood
- mood changes broadcast over WS; frontend shows a mood badge with
affection tooltip; also handles the 'reminder' message type from the
reminder scheduler
- tests: sentiment scoring, mood shift, affection bounds, prompt line,
onChange callback
- SearchModal queries GET /messages/search and jumps to the matching
session; opened from the sidebar button or Ctrl+K
- session items in the sidebar gain a hover export button that
downloads the markdown export via an authenticated fetch + blob
(plain links cannot send the JWT)
- web-search: real SearXNG /search?format=json query, top-5 formatted
results (was a 'not configured' stub)
- web-fetch: fetch page, strip scripts/styles/tags, truncate to 4000
runes for the LLM context
- http: generic request tool (GET/POST/PUT/DELETE/PATCH) with scheme
and method validation, status + truncated body
- file: sandboxed read/write/list inside the data directory with path
escape rejection; split into file_read/file_write/file_list
- all tool schemas converted to proper JSON Schema; httptest/tempdir
tests for each plugin
- engine: 5-field cron parser (*/n, ranges, lists) firing at most once
per minute; device-state triggers with edge detection; manual trigger
via POST /automation/rules/:id/trigger; actions run through the AI
pipeline so rules can call any tool (IoT control, search, ...)
- reminders: due-scan scheduler delivering via WebSocket broadcast,
optional LLM-polished reminder text with plain-title fallback;
Reminder gains Done flag with ListDue/MarkDone
- tests: cron matching and validation, once-per-minute dedup, device
edge triggering, manual trigger, reminder delivery
- supervisor loop keeps the OBv11 WebSocket alive, reconnecting with
exponential backoff (1s→30s) when the connection drops; Start no
longer fails permanently when NapCat is temporarily unreachable
- Send enforces MinGap between messages (from SendOptions or the
adapter's send_interval_ms setting, now wired from the admin panel)
- parseMessage no longer panics on events missing sender/time fields
- tests: reconnect after drop, rate limit enforcement, malformed events,
send while disconnected
- orchestrator: Tracer interface with spans for intent analysis, persona,
subsession dispatch, context build, LLM streaming and each tool call;
traces finish when the stream completes — the admin Trace waterfall
finally receives data
- background: Thinker generates real LLM thoughts (post-chat + silence
triggers, recent history as context, empty/overlong outputs discarded);
thoughts broadcast to web clients as 'thinking' WS messages; pipeline
postChatHook triggers post-chat thinking after each round
- memory: ApplyDecayAll + ListUserIDs with a 6h decay/purge cron in main
(was implemented but never scheduled)
- repository: optional pgvector embedding column (migration with
CREATE EXTENSION guarded by main), SearchByVector cosine search,
vector-enabled insert path
- service: SetEmbedder — memories are embedded on Add; Search merges
vector hits with n-gram keyword matches (vector first, deduped)
- embedder was already implemented (OpenAI-compatible API + n-gram FNV
fallback) but never instantiated; main now wires it from
EMBEDDING_API_URL/EMBEDDING_MODEL config
- Sidebar lists sessions with title/relative time, click to switch,
hover-to-delete; refreshes after each round so new titles appear
- ChatPage loads message history from GET /sessions/:id/messages when
entering a session
- WS connect now passes user_id so persisted messages have an owner
- store: new chat_sessions/chat_messages tables with CRUD, ILIKE message
search, and auto-titling from the first user message
- context: optional Persister on the sliding-window store — messages are
saved asynchronously on Add, sessions not in memory are restored from
the database on Get
- orchestrator: ProcessInput/FinishRound now carry userID so persisted
messages have an owner
- api: real session handlers (list/create/delete), message history and
clear endpoints, working message search and three-format export
(markdown/txt/json), fixing the short-ID panic in export
- main: migration, store wiring, ctxStore persister adapter
Tests: chat store handler lifecycle (fake store), context persister
save/restore, stub auth login flow updated.
The bare 'cyrene' .gitignore pattern matched the cmd/cyrene directory,
so trace_adapter.go was never committed and a fresh clone failed to
build (undefined: traceAdapter). Scope the pattern to the root binary
and add the missing file.