outreach: per-user voice from own emails + transparency; active-thread context (v0.1.0:70)

Voice upgrade. draft_outreach now learns the SENDER's voice: the codified rules PLUS a
few-shot of that user's own recent sent emails (_voice_examples; from_email = the
sender, de-identified in the same scrub batch as the recipient context, reference-only).
The response returns which of the sender's emails were used (subject + date + recipient),
shown in the UI as "Voice based on: …" — transparency to avoid the black-box problem.
Falls back to rules-only with a clear note when the user has no captured sent email.

Context restructured: _context groups the investor's email by thread and labels the most
recent thread as the "Active conversation (what you are replying to)" with earlier emails
as background, so replies stay on-topic instead of dredging old threads.

Sender email resolved in handle_outreach_draft (users table by user_id). Test extended
(active/background split, voice examples + meta, no-sender fallback). Fixed a UI bug the
preview caught: the manual Draft button was onClick={draft}, which passed the click event
as the investor arg after draft() gained params -> circular-JSON error; now onClick={()=>draft()}.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Keysat
2026-06-08 22:06:38 -05:00
parent 787d580550
commit 49f84ca9a4
7 changed files with 142 additions and 40 deletions
+7 -1
View File
@@ -3948,8 +3948,14 @@ class CRMHandler(BaseHTTPRequestHandler):
return self.send_error_json("investor_id required", 400)
conn = get_db()
try:
sender_email = None
try:
r = conn.execute("SELECT email FROM users WHERE id=?", (user.get('user_id'),)).fetchone()
sender_email = r[0] if r else None
except Exception:
pass
res = _outreach_agent.draft_outreach(conn, inv, body.get('outreach_type', 'follow_up'),
body.get('guidance', '') or '', DB_PATH)
body.get('guidance', '') or '', DB_PATH, sender_email=sender_email)
try:
conn.execute(
"INSERT INTO interaction_log (id, ts, actor_type, actor_id, action, target_type, target_id, payload, source, created_at) "