fix(persona-chat): review fixes before merge

- Thread tools are bound server-side to the chat's own thread. The model
  used to pass contact_id, so practice mode could read any conversation it
  named; a model-supplied contact_id is now only checked, never trusted, and
  the prompts no longer hand it one.
- Practice chats need no persona. They're stored under PRACTICE_PERSONA_ID
  and run with an inert stand-in, so deleting or editing personas can't hide
  or break them. persona_id is optional on create for as_contact.
- The per-thread list filters in SQL (list_persona_chats_for_contact, served
  by idx_persona_chat_anchor_contact) instead of decoding every transcript.
- fetch_mms_image checks the content type and a 20 MB cap before reading
  the body, so a video attachment isn't downloaded in full.
- get_photos_near_time unions tags over every path sharing the content, per
  the multi-library model.
- look_at_photo only opens indexed photos, so an excluded folder can't be
  read by guessing a path.
- One date parser: get_photos_near_time uses date_info::parse_center.
- Practice prompt source rewrapped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Cameron CordesandClaude Opus 5.5 committed 2026-10-20 19:38:08 -04:00
1 parent 185a1b561a
commit e029c81274
8 files changed
+383 -116

No files matched your search

+13 -7
View File
@@ -578,13 +578,19 @@ GET /insights/chat/history?path=... (rendered transcript with tool invocations)
POST /insights/chat/rewind (truncate transcript at a rendered index)
// Persona chat — "Continue with persona" from an SMS thread (src/ai/sms_anchor.rs)
POST /persona_chat/conversations (body: { persona_id, anchor?: { contact_id, message_id } };
anchor snapshots ~40 SMS-API messages up to message_id into
the row, 502 if SMS-API can't read the thread)
GET /persona_chat/conversations?contact_id=N (only chats anchored on that thread)
// Anchored turns append the thread + chat-register guidance to the persona prompt and add
// the get_thread_messages / look_at_message_image / get_photos_near_time / look_at_photo
// tools (ToolGateOpts.sms_thread_tools). Image tools use the LOCAL vision model.
POST /persona_chat/conversations (body: { persona_id, anchor?: { contact_id, message_id,
mode?: "about" | "as_contact" } }; anchor snapshots ~40
SMS-API messages up to message_id into the row, 502 if
SMS-API can't read the thread. `as_contact` (practice) needs
no persona: stored under PRACTICE_PERSONA_ID, listed as
"Practice")
GET /persona_chat/conversations?contact_id=N (only chats anchored on that thread; DAO query)
// Anchored turns append the thread + mode guidance to the prompt and add the thread
// tools (ToolGateOpts.sms_thread_tools): get_thread_messages / look_at_message_image are
// bound server-side to the chat's own thread (a model-named contact_id is refused), and
// about-mode also gets get_photos_near_time / look_at_photo. Practice keeps only the two
// thread tools. Image tools use the LOCAL vision model; MMS downloads are image-only and
// capped (MMS_IMAGE_MAX_BYTES); look_at_photo only opens indexed photos.
// User AI Prefs (owner defaults + passive mirror; src/prefs.rs)
GET /prefs (current row; auth)