Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/web/__tests__/eval/meeting-follow-up-draft.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const ROLLOUT_SUMMARY: MeetingSummary = {
{
description:
"Tell the customers who already have the fifteenth in writing",
owner: null,
},
],
openQuestions: ["Who owns the migration webinar?"],
Expand All @@ -45,6 +46,7 @@ const UNRESOLVED_PRICING_SUMMARY: MeetingSummary = {
openQuestions: [
"Do they pay for all forty people or only the fifteen daily users?",
],
nextSteps: [],
};

describe.runIf(shouldRunEval)("meeting-follow-up-draft eval", () => {
Expand Down Expand Up @@ -146,7 +148,7 @@ describe.runIf(shouldRunEval)("meeting-follow-up-draft eval", () => {
criterion: {
name: "Ready to send",
description:
"The email is addressed to the group and could be sent as written. It contains no unfilled placeholders for the sender to complete, no meta-commentary about being AI-generated, and no instructions to the reader about how to use the draft.",
"The email is addressed to the group and could be sent as written. It contains no unfilled placeholders for the sender to complete, no meta-commentary about being AI-generated, and no instructions to the reader about how to use the draft. Explicitly named action owners remain the owners; work that the source leaves unassigned may be omitted or described without an owner and must not be treated as a drafting placeholder.",
},
input: JSON.stringify(ROLLOUT_SUMMARY, null, 2),
output: `${draft.subject}\n\n${draft.body}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ The user reviews and sends this themselves, so write it as them, ready to send.
Rules:
- Only reference what the meeting actually covered. Never invent a commitment, a deadline, a price or a next step that was not agreed.
- Do not promise anything on the user's behalf that they did not say they would do.
- Preserve the owner of every named commitment. Do not rewrite another person's action as "I will"; use first person only when the summary establishes that the sender owns the action.
- When an action has no owner, keep it unassigned and describe the agreed work without inventing an owner or turning the email into a request to fill in missing information.
- The sender is identified by email. Do not assume the sender is any differently named owner in the summary.
- Recap only what is useful to the recipients. This is an email, not a transcript: a few short paragraphs or a small list.
- Address the recipients as a group. Do not open with a placeholder like "[Name]".
- If a writing style is provided, match the user's tone and formality.
Expand Down Expand Up @@ -132,5 +135,7 @@ ${JSON.stringify(summary, null, 2)}
</summary>

${getTodayForLLM()}
Write the follow-up email as ${emailAccount.email}.`;
Sender: ${emailAccount.email}

Write the follow-up email as the sender.`;
}
34 changes: 25 additions & 9 deletions apps/web/utils/ai/meeting-recorder/summarize-meeting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ Rules:
- When the meeting reverses an earlier decision, report the final position, not the one that was superseded.
- If a speaker label looks wrong or two speakers have similar names, prefer describing what was decided over who said it.
- Leave a section empty when the meeting genuinely had nothing for it. An empty list is better than a filler entry.
- Put each fact in the single section that best describes it. A decision is a settled choice, not merely a commitment or deadline. Do not repeat an action item as a next step or restate a decision in another section.
- Combine closely related work into one action item when the same person took responsibility for the same outcome.
- Keep an action's deadline or timing in that action item's description. Do not move task timing into next steps.
- Keep the length proportionate to the meeting. For a short, single-topic meeting, use a brief overview and only the distinct details someone needs to remember.
- Write in the language the meeting was held in.
- Write for someone who attended and wants a reminder, not for someone who needs the meeting re-narrated.

Expand All @@ -27,29 +31,41 @@ Return your response in JSON format.`;
const summarySchema = z.object({
overview: z
.string()
.describe("A short paragraph covering what the meeting was about"),
.describe(
"One or two sentences covering what the meeting was about, without repeating the detailed lists",
),
keyDecisions: z
.array(z.string())
.describe("Decisions the group actually settled on"),
.describe(
"Choices the group actually settled on; exclude action items, deadlines and status updates",
),
actionItems: z
.array(
z.object({
description: z.string(),
description: z
.string()
.describe(
"The agreed work and any deadline or timing attached to that work",
),
owner: z
.string()
.optional()
.describe("Only set when the transcript shows who took this on"),
.nullable()
.describe(
"The person who took this on, or null when the transcript does not establish an owner",
),
}),
)
.describe("Concrete follow-up work agreed in the meeting"),
.describe(
"Distinct concrete follow-up work agreed in the meeting, combining closely related work with the same owner",
),
openQuestions: z
.array(z.string())
.optional()
.describe("Questions raised but left unresolved"),
nextSteps: z
.array(z.string())
.optional()
.describe("What happens next, including any agreed timing"),
.describe(
"Separate scheduled events such as another meeting; never include action deadlines or restate action items, and use an empty list when there is no separate event",
),
});

export type MeetingSummary = z.infer<typeof summarySchema>;
Expand Down
2 changes: 2 additions & 0 deletions apps/web/utils/meeting-recorder/send-recap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ describe("sendMeetingRecapEmail", () => {
overview: "Discussed the plan.",
keyDecisions: [],
actionItems: [],
openQuestions: [],
nextSteps: [],
},
followUpDraftCreated: false,
logger: {
Expand Down
2 changes: 1 addition & 1 deletion packages/resend/emails/meeting-recap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {

export type MeetingRecapActionItem = {
description: string;
owner?: string;
owner?: string | null;
};

export type MeetingRecapContent = {
Expand Down
Loading