feat(escalations): magic-moment 3-option CTA + claim 500 fix

- HandoffContextScreen: 3-option layout (Continue/AI analysis/Own thing)
  with hasTaskLane, activeOptionKey, spinner/disabled states
- AssistantChatPage: wire up handleContinue, handleAIAnalysis, handleOwnThing
  handlers; chip detail expansion inline with copy-button fix; post-escalation
  redirect to dashboard on ConcludeSessionModal close
- TaskLane: fix async copy button (await + execCommand fallback + copiedKey
  visual feedback); whitespace-pre-wrap on command blocks
- Fix 500 on claim: Pydantic v2 model_validate() + model_copy(update={})
  (was passing update= kwarg directly which v2 rejects)
- HandoffResponse schema: handed_off_by_name field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-30 00:05:02 -04:00
parent fb2dc222fd
commit db717b0b3f
11 changed files with 673 additions and 207 deletions

View File

@@ -913,6 +913,41 @@ async def generate_status_update(
"""Generate a status update for ticket notes, client communication, or email draft."""
session = await _load_session(session_id, user_id, db)
# For escalation/ticket_notes, return the pre-generated handoff prose immediately
# if enrich_escalation_async has already populated it. This eliminates the
# redundant Sonnet re-summarization on every "Ticket Notes" click.
if request.context == "escalation" and request.audience == "ticket_notes":
from app.models.session_handoff import SessionHandoff
handoff_q = await db.execute(
select(SessionHandoff)
.where(
SessionHandoff.session_id == session_id,
SessionHandoff.intent == "escalate",
)
.order_by(SessionHandoff.created_at.desc())
.limit(1)
)
escalation_handoff = handoff_q.scalar_one_or_none()
saved_data = (
escalation_handoff.ai_assessment_data or {}
) if escalation_handoff else {}
prose = saved_data.get("summary_prose") or (
escalation_handoff.ai_assessment if escalation_handoff else None
)
if prose:
return StatusUpdateResponse(
content=prose,
audience=request.audience,
length=request.length,
context=request.context,
session_status=session.status,
steps_completed=session.step_count or 0,
time_spent_display=None,
client_name=None,
generated_at=datetime.now(timezone.utc),
)
# Build conversation summary from session steps
steps_summary = []
for step in sorted(session.steps, key=lambda s: s.step_order):