handoff fixes

This commit is contained in:
debu-2 2026-10-06 02:44:54 +05:30
parent 6c52cd19cb
commit 89ddebdb60
15 changed files with 991 additions and 271 deletions

View file

@ -1,12 +1,5 @@
from html import escape
DEBUG_REASON_MARKERS = (
"step passes",
"classifier",
"rule evaluation",
"no handoff instructions were set",
)
def _clean_external_subject(subject: str) -> str:
value = (subject or "(No subject)").strip()
@ -28,171 +21,129 @@ def external_reply_subject(external_subject: str) -> str:
return f"Re: {cleaned}"
def format_reason_for_human(reason: str | None, *, is_update: bool = False) -> str | None:
if is_update:
return None
if not reason or not reason.strip():
return None
lower = reason.strip().lower()
if any(marker in lower for marker in DEBUG_REASON_MARKERS):
return "This conversation matched your Human Handoff settings."
if lower in {
"matched handoff rules.",
"new message in the linked conversation.",
}:
return "This conversation requires human attention."
return reason.strip()
def format_sender_line(sender: dict) -> str:
name = (sender.get("name") or "").strip()
address = (sender.get("address") or "").strip()
if not address:
return "unknown sender"
local = address.split("@", 1)[0].lower()
if name and name.lower() not in {local, address.lower()}:
return f"{name} <{address}>"
return address
def build_initial_handoff_text(
*,
external_sender: str,
external_subject: str,
external_body: str,
agent_email: str,
handoff_reason: str | None,
) -> str:
reason = format_reason_for_human(handoff_reason)
lines = [
"GENTIC — HUMAN HANDOFF",
"",
"This conversation needs your attention.",
"",
f"From: {external_sender}",
f"Subject: {_clean_external_subject(external_subject)}",
"",
"LATEST MESSAGE",
"",
external_body.strip(),
"",
]
if reason:
lines.extend(["WHY YOU'RE RECEIVING THIS", "", reason, ""])
lines.extend(
[
"REPLY NORMALLY",
"",
f"Reply to this email and Gentic will send only your new reply to the "
f"original sender through {agent_email}.",
"",
"This private handoff conversation and quoted history will not be shared "
"with the sender.",
"",
"—",
"Gentic Human Handoff",
"Private Agent ↔ Human conversation",
]
def _footer_html(agent_email: str) -> str:
return f"""
<div class="gentic-handoff-footer" style="background:#F8F7FC;border-top:1px solid #E9E5F2;padding:12px 20px;">
<p style="margin:0 0 6px;font-size:12px;font-weight:650;color:#6750E8;">↩ Reply normally</p>
<p style="margin:0;font-size:11px;line-height:1.45;color:#77758A;">
Your reply will be sent to the original sender through
<span style="color:#292536;">{escape(agent_email)}</span>.
Only the new text you write will be sent.
</p>
</div>"""
def _footer_text(agent_email: str) -> str:
return (
"↩ Reply normally\n\n"
f"Your reply will be sent to the original sender through {agent_email}.\n"
"Only the new text you write will be sent."
)
return "\n".join(lines)
def build_initial_handoff_html(
def _header_html(sender_line: str, external_subject: str) -> str:
subject = escape(_clean_external_subject(external_subject))
return f"""
<div class="gentic-handoff-header" style="padding:16px 20px 12px;border-bottom:1px solid #ECE9EF;">
<div style="font-size:14px;font-weight:650;color:#6750E8;">✦ Human Handoff</div>
<div style="margin-top:5px;font-size:12px;color:#6F6B7C;">
<span>{escape(sender_line)}</span>
<span style="color:#292536;font-weight:550;"> | {subject}</span>
</div>
</div>"""
def _header_text(sender_line: str, external_subject: str) -> str:
return f"✦ Human Handoff\n{sender_line} | {_clean_external_subject(external_subject)}"
def build_mirror_handoff_html(
*,
external_sender: str,
sender_line: str,
external_subject: str,
external_body: str,
original_html: str,
agent_email: str,
handoff_reason: str | None,
) -> str:
reason = format_reason_for_human(handoff_reason)
reason_block = ""
if reason:
reason_block = f"""
<tr><td style="padding:18px 24px 0;">
<p style="margin:0 0 8px;font-size:11px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:#8A8695;">Why you&apos;re receiving this</p>
<p style="margin:0;font-size:13px;line-height:1.55;color:#4A4652;">{escape(reason)}</p>
</td></tr>"""
return f"""<!DOCTYPE html>
<html><body style="margin:0;padding:0;background:#F5F4F2;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#F5F4F2;padding:24px 12px;">
<tr><td align="center">
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="width:100%;max-width:600px;background:#FFFFFF;border:1px solid #E8E5EA;border-radius:12px;overflow:hidden;">
<tr><td style="padding:24px 24px 8px;">
<p style="margin:0 0 4px;font-size:12px;font-weight:600;color:#7357F6;">✦ Gentic</p>
<h1 style="margin:0 0 8px;font-size:20px;font-weight:700;color:#171424;">Human Handoff</h1>
<p style="margin:0;font-size:13px;line-height:1.5;color:#77758A;">This conversation needs your attention.</p>
</td></tr>
<tr><td style="padding:8px 24px 0;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#FAF9FB;border:1px solid #ECE9EF;border-radius:8px;">
<tr><td style="padding:14px;">
<p style="margin:0 0 10px;font-size:10px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:#8A8695;">From</p>
<p style="margin:0 0 14px;font-size:13px;font-weight:500;color:#292536;">{escape(external_sender)}</p>
<p style="margin:0 0 10px;font-size:10px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:#8A8695;">Subject</p>
<p style="margin:0;font-size:13px;font-weight:500;color:#292536;">{escape(_clean_external_subject(external_subject))}</p>
</td></tr>
</table>
</td></tr>
<tr><td style="padding:18px 24px 0;">
<p style="margin:0 0 8px;font-size:11px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:#8A8695;">Latest message</p>
<div style="border-left:3px solid #7357F6;padding:4px 0 4px 14px;font-size:14px;line-height:1.55;color:#292536;white-space:pre-wrap;">{escape(external_body.strip())}</div>
</td></tr>
{reason_block}
<tr><td style="padding:18px 24px 24px;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#F7F5FF;border:1px solid #E5DFFF;border-radius:8px;">
<tr><td style="padding:14px;">
<p style="margin:0 0 8px;font-size:13px;font-weight:650;color:#5633CE;">↩ Reply normally</p>
<p style="margin:0;font-size:12px;line-height:1.5;color:#625D70;">
Reply to this email and Gentic will send your response to the original sender as
<strong>{escape(agent_email)}</strong>. Only the new text you write will be sent.
This handoff email and quoted history stay private.
</p>
</td></tr>
</table>
</td></tr>
<tr><td style="padding:0 24px 24px;text-align:center;">
<p style="margin:0;font-size:10px;color:#A19DAA;">Gentic Human Handoff<br>Private Agent ↔ Human conversation</p>
</td></tr>
</table>
</td></tr>
</table>
<html><body style="margin:0;padding:0;background:#FFFFFF;">
{_header_html(sender_line, external_subject)}
<div class="gentic-handoff-original" style="padding:20px 20px 24px;">
{original_html}
</div>
{_footer_html(agent_email)}
</body></html>"""
def build_update_handoff_text(
def build_mirror_handoff_text(
*,
external_sender: str,
external_body: str,
sender_line: str,
external_subject: str,
original_body_text: str,
agent_email: str,
) -> str:
return "\n".join(
return "\n\n".join(
[
"Customer replied",
"",
external_sender,
"",
external_body.strip(),
"",
f"Reply normally to respond through {agent_email}.",
_header_text(sender_line, external_subject),
"─" * 56,
original_body_text.strip(),
"─" * 56,
_footer_text(agent_email),
]
)
def build_update_handoff_html(
def build_mirror_update_html(
*,
external_sender: str,
external_body: str,
sender_line: str,
external_subject: str,
original_html: str,
agent_email: str,
) -> str:
subject = escape(_clean_external_subject(external_subject))
return f"""<!DOCTYPE html>
<html><body style="margin:0;padding:0;background:#F5F4F2;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#F5F4F2;padding:24px 12px;">
<tr><td align="center">
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="width:100%;max-width:600px;background:#FFFFFF;border:1px solid #E8E5EA;border-radius:12px;">
<tr><td style="padding:24px;">
<p style="margin:0 0 8px;font-size:12px;font-weight:600;color:#7357F6;">✦ Gentic</p>
<h1 style="margin:0 0 16px;font-size:18px;font-weight:700;color:#171424;">Customer replied</h1>
<p style="margin:0 0 12px;font-size:13px;font-weight:500;color:#292536;">{escape(external_sender)}</p>
<div style="border-left:3px solid #7357F6;padding:4px 0 4px 14px;font-size:14px;line-height:1.55;color:#292536;white-space:pre-wrap;">{escape(external_body.strip())}</div>
<p style="margin:18px 0 0;font-size:12px;line-height:1.5;color:#625D70;">
Reply normally to respond through <strong>{escape(agent_email)}</strong>.
</p>
</td></tr>
</table>
</td></tr>
</table>
<html><body style="margin:0;padding:0;background:#FFFFFF;">
<div class="gentic-handoff-header" style="padding:16px 20px 12px;border-bottom:1px solid #ECE9EF;">
<div style="font-size:14px;font-weight:650;color:#6750E8;">✦ Customer replied</div>
<div style="margin-top:5px;font-size:12px;color:#6F6B7C;">
<span>{escape(sender_line)}</span>
<span style="color:#292536;font-weight:550;"> | {subject}</span>
</div>
</div>
<div class="gentic-handoff-original" style="padding:20px 20px 24px;">
{original_html}
</div>
{_footer_html(agent_email)}
</body></html>"""
def build_mirror_update_text(
*,
sender_line: str,
external_subject: str,
original_body_text: str,
agent_email: str,
) -> str:
return "\n\n".join(
[
f"✦ Customer replied\n{sender_line} | {_clean_external_subject(external_subject)}",
"─" * 56,
original_body_text.strip(),
"─" * 56,
_footer_text(agent_email),
]
)
def build_relay_confirmation_text(customer_body: str) -> str:
return f"Sent to customer:\n\n{customer_body.strip()}"