meetingEmailPrompts.ts5.4 KBView on GitHub import type { CalendarEvent } from '../types/calendar-types';
import { useCedarStore } from '@/modules/store';
/**
* Prompts that ask the agent to draft an email about a specific meeting.
*
* These build the WHOLE prompt from the event — title, window, attendees, conferencing —
* because they're fired from a button or a hotkey with nothing typed. They go out via
* `setOverrideInputContent(..., { autoSend: true })`, which submits immediately instead of
* parking the text in the composer for the user to edit.
*/
/** ISO strings are unreadable in a prompt; give the agent something it can quote back. */
function formatWindow(event: CalendarEvent): string {
const start = event.start?.dateTime ?? event.start?.date;
const end = event.end?.dateTime ?? event.end?.date;
if (!start) return 'an unknown time';
const startDate = new Date(start);
const startLabel = startDate.toLocaleString('en-US', {
weekday: 'long',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
});
if (!end) return startLabel;
const endLabel = new Date(end).toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit',
});
return `${startLabel} – ${endLabel}`;
}
/** Everyone but the user — they're the ones the email goes to. */
function describeAttendees(event: CalendarEvent): string {
const others = (event.attendees ?? []).filter((a) => !a.self && a.email);
if (others.length === 0) return 'no other attendees listed';
return others
.map((a) => (a.displayName ? `${a.displayName} <${a.email}>` : (a.email ?? '')))
.join(', ');
}
/**
* "They didn't show" — ask for a combined no-show + reschedule email for a meeting that is
* happening right now. One draft rather than two: in practice the no-show note and the
* "when can we redo this?" ask are the same email.
*/
export function buildNoShowPrompt(event: CalendarEvent): string {
const title = event.summary || 'Untitled meeting';
const meetLink = event.conferenceData?.entryPoints?.[0]?.uri;
return [
`The attendees did not show up to "${title}" (${formatWindow(event)}).`,
`Attendees: ${describeAttendees(event)}.`,
meetLink ? `Meeting link: ${meetLink}.` : null,
'',
'Draft a short, friendly email to them that notes we missed each other, keeps all blame off',
'them, and proposes rescheduling — offer a couple of concrete alternative times based on my',
'calendar availability. Keep it brief and in my voice.',
]
.filter(Boolean)
.join('\n');
}
/**
* "We need to move this" — a reschedule ask for a meeting that hasn't happened yet (or is
* happening now). Unlike the no-show prompt this carries no implication that anyone missed it.
*/
export function buildReschedulePrompt(event: CalendarEvent): string {
const title = event.summary || 'Untitled meeting';
return [
`I need to reschedule "${title}" (${formatWindow(event)}).`,
`Attendees: ${describeAttendees(event)}.`,
'',
'Draft a short, apologetic email asking to move it. Offer a couple of concrete alternative',
'times based on my calendar availability, and keep it brief and in my voice.',
].join('\n');
}
/**
* Send a meeting prompt to the chat and submit it straight away. Used from surfaces that have
* no composer of their own (the agenda block's "No show" button, the calendar hotkeys).
*/
export function sendMeetingPrompt(prompt: string): void {
useCedarStore.getState().setOverrideInputContent(prompt, { autoSend: true });
}
/** Convenience wrapper: build the no-show prompt for `event` and fire it. */
export function sendNoShowPrompt(event: CalendarEvent): void {
sendMeetingPrompt(buildNoShowPrompt(event));
}
/** Convenience wrapper: build the reschedule prompt for `event` and fire it. */
export function sendReschedulePrompt(event: CalendarEvent): void {
sendMeetingPrompt(buildReschedulePrompt(event));
}
/**
* "That's a wrap" — a recap-and-next-steps email for a meeting that has already happened.
*
* The third member of this family, added for the Meetings tab's `after` phase. It differs
* from the other two in what it points the agent AT: a no-show or reschedule ask can be
* written from the invitation alone, but a recap is only worth sending if it reflects what
* was actually said. So when the meeting has notes, the prompt names them and tells the
* agent to lead with them — the notes doc and any transcript are already in the agent's
* reach, and asking for a recap without pointing at them produces generic filler.
*/
export function buildRecapPrompt(event: CalendarEvent, hasNotes: boolean): string {
const title = event.summary || 'Untitled meeting';
return [
`We just finished "${title}" (${formatWindow(event)}).`,
`Attendees: ${describeAttendees(event)}.`,
hasNotes
? 'Read my notes on this meeting and the transcript if there is one, and lead with what I wrote — my notes are what I actually care about.'
: 'Use the transcript if there is one; otherwise work from the deal context.',
'',
'Draft a short recap email to them: what we agreed, the open questions, and the concrete',
'next step with an owner and a date. No preamble, no restating the agenda, in my voice.',
].join('\n');
}
/** Convenience wrapper: build the recap prompt for `event` and fire it. */
export function sendRecapPrompt(event: CalendarEvent, hasNotes: boolean): void {
sendMeetingPrompt(buildRecapPrompt(event, hasNotes));
}