apps/web/src/lib/aiHandoff.ts
1import type { ChatMessage } from '@local/ai'
2import { HANDOFF_FAILURE_REPLY } from '@local/ai/components/workflows'
3
4/** Recover the displayed selection, excluding explicitly separate alternatives. */
5export function selectedAgendaUrls(history: ChatMessage[]): string[] {
6 const plan = [...history]
7 .reverse()
8 .find(
9 (turn) =>
10 turn.role === 'assistant' && turn.text.startsWith('Your UNLEASH in '),
11 )?.text
12 if (!plan) return []
13 const selected = plan.split(/\*\*Alternatives|Would you like me to email/)[0]
14 return [
15 ...new Set(
16 [...selected.matchAll(/\]\(([^)]+agendaSession=[^)]+)\)/g)].map(
17 (match) => match[1],
18 ),
19 ),
20 ]
21}
22
23export type HandoffKind = 'lead' | 'agenda-email'
24export type HandoffOutcome = {
25 kind: HandoffKind
26 ok: boolean
27 ticketEnquiry?: boolean
28 validationReply?: string
29}
30
31/** A generated promise is never evidence that a handoff happened. */
32export function verifiedHandoffReply(
33 text: string,
34 outcome?: HandoffOutcome,
35): string {
36 if (outcome?.validationReply && !outcome.ok) return outcome.validationReply
37 if (outcome)
38 return outcome.ok
39 ? outcome.kind === 'agenda-email'
40 ? 'Your selected agenda has been submitted for email delivery.'
41 : `Your enquiry has been sent to the team.${outcome.ticketEnquiry ? ' This is an enquiry, not a confirmed booking.' : ''}`
42 : HANDOFF_FAILURE_REPLY
43 if (
44 /\b(?:we(?:['’]ve| have)?|i(?:['’]ve| have)?|your (?:request|enquiry|agenda))[^.!?\n]{0,65}\b(?:sent|submitted|forwarded|passed|sending|will send|will be sent)\b|\b(?:done|team will be in touch|(?:request|enquiry|agenda) is on its way)\b/i.test(
45 text,
46 )
47 )
48 return HANDOFF_FAILURE_REPLY
49 return text
50}
51