callwright 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +16 -0
- package/.env.example +21 -0
- package/Dockerfile +18 -0
- package/LICENSE +21 -0
- package/README.md +239 -0
- package/config.example.json +18 -0
- package/dispatch-core.js +371 -0
- package/dispatch.js +55 -0
- package/docs/add-language-spec.md +207 -0
- package/docs/profile-learning-spec.md +134 -0
- package/examples/job.appointment-confirmation.json +24 -0
- package/examples/job.general-inquiry.ja.json +16 -0
- package/examples/job.reservation.json +19 -0
- package/fly.toml +35 -0
- package/generic-prompt.ja.md +125 -0
- package/generic-prompt.md +149 -0
- package/get-call.js +70 -0
- package/grounding.example.json +17 -0
- package/init.js +159 -0
- package/lang-core.js +476 -0
- package/lang-phrases.json +29 -0
- package/learn-core.js +257 -0
- package/learn.js +65 -0
- package/notify-core.js +113 -0
- package/package.json +77 -0
- package/paths.js +37 -0
- package/place_call.schema.json +173 -0
- package/providers/README.md +116 -0
- package/retry-core.js +121 -0
- package/scenario-profiles.json +112 -0
- package/server.js +876 -0
- package/setup-agent-from.js +96 -0
- package/setup-core.js +299 -0
- package/update-prompt.js +98 -0
- package/webhook/README.md +104 -0
- package/webhook/api/retell-webhook.js +111 -0
- package/webhook/package.json +13 -0
- package/webhook/vercel.json +9 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// Retell -> Resend email notifier (serverless, scale-to-zero).
|
|
2
|
+
// Deploy on Vercel as /api/retell-webhook. Runs ONLY when Retell posts a
|
|
3
|
+
// completed call, then emails a status summary. No always-on server, no DB.
|
|
4
|
+
//
|
|
5
|
+
// Env (Vercel project settings):
|
|
6
|
+
// RETELL_API_KEY - to verify the webhook signature
|
|
7
|
+
// RESEND_API_KEY - your Resend key
|
|
8
|
+
// EMAIL_FROM - a verified Resend sender, e.g. "calls@yourdomain.com"
|
|
9
|
+
// EMAIL_TO - where to send status emails (your inbox)
|
|
10
|
+
|
|
11
|
+
const { Resend } = require("resend");
|
|
12
|
+
|
|
13
|
+
// Build the email subject + html from a Retell call object. Exported for testing.
|
|
14
|
+
function buildEmail(call) {
|
|
15
|
+
const v = call.retell_llm_dynamic_variables || {};
|
|
16
|
+
const a = call.call_analysis || {};
|
|
17
|
+
const c = a.custom_analysis_data || {};
|
|
18
|
+
|
|
19
|
+
const business = v.business_name || call.to_number || "(unknown)";
|
|
20
|
+
const objective = v.objective || v.objective_detail || "call";
|
|
21
|
+
const status = c.status || (a.call_successful ? "completed" : "failed");
|
|
22
|
+
const dur = call.duration_ms ? Math.round(call.duration_ms / 1000) + "s" : "n/a";
|
|
23
|
+
|
|
24
|
+
const emoji = { booked: "â
", failed: "â", voicemail: "đ",
|
|
25
|
+
callback_needed: "âŠī¸", escalated: "â ī¸" }[status] || "đ";
|
|
26
|
+
|
|
27
|
+
const subject = `${emoji} [${status}] ${business} â ${objective}`;
|
|
28
|
+
|
|
29
|
+
const callIdLink = call.call_id
|
|
30
|
+
? `<a href="https://dashboard.retellai.com/call-history?history=${call.call_id}" style="color:#0066cc;text-decoration:none">${call.call_id}</a>`
|
|
31
|
+
: null;
|
|
32
|
+
|
|
33
|
+
const rows = [
|
|
34
|
+
["Status", status],
|
|
35
|
+
["Business", business],
|
|
36
|
+
["Objective", objective],
|
|
37
|
+
["Booked date", c.booked_date],
|
|
38
|
+
["Booked time", c.booked_time],
|
|
39
|
+
["Confirmation", c.confirmation_ref],
|
|
40
|
+
["Accommodations OK", c.accommodations_ok],
|
|
41
|
+
["Unmet items", c.unmet_items],
|
|
42
|
+
["Unanswered questions", c.unanswered_questions],
|
|
43
|
+
["Duration", dur],
|
|
44
|
+
["Disconnect", call.disconnection_reason],
|
|
45
|
+
["Call ID", callIdLink],
|
|
46
|
+
].filter(([, val]) => val !== undefined && val !== null && val !== "");
|
|
47
|
+
|
|
48
|
+
const tableRows = rows
|
|
49
|
+
.map(([k, val]) => `<tr><td style="padding:4px 10px;color:#666">${k}</td><td style="padding:4px 10px"><b>${val}</b></td></tr>`)
|
|
50
|
+
.join("");
|
|
51
|
+
|
|
52
|
+
const summary = a.call_summary ? `<p style="margin:12px 0">${a.call_summary}</p>` : "";
|
|
53
|
+
const transcript = call.transcript
|
|
54
|
+
? `<details><summary style="cursor:pointer;color:#666">Transcript</summary><pre style="white-space:pre-wrap;font-size:13px;background:#f6f6f6;padding:10px;border-radius:6px">${escapeHtml(call.transcript)}</pre></details>`
|
|
55
|
+
: "";
|
|
56
|
+
|
|
57
|
+
const html = `<div style="font-family:system-ui,sans-serif;max-width:560px">
|
|
58
|
+
<h2 style="margin:0 0 6px">${emoji} ${objective}</h2>
|
|
59
|
+
<div style="color:#666;margin-bottom:10px">${business}</div>
|
|
60
|
+
${summary}
|
|
61
|
+
<table style="border-collapse:collapse;font-size:14px">${tableRows}</table>
|
|
62
|
+
<div style="margin-top:14px">${transcript}</div>
|
|
63
|
+
</div>`;
|
|
64
|
+
|
|
65
|
+
return { subject, html };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function escapeHtml(s) {
|
|
69
|
+
return String(s).replace(/[&<>"']/g, (ch) =>
|
|
70
|
+
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[ch]));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Vercel serverless handler.
|
|
74
|
+
module.exports = async (req, res) => {
|
|
75
|
+
if (req.method !== "POST") return res.status(405).end("POST only");
|
|
76
|
+
|
|
77
|
+
// Verify the webhook is from Retell (signature over the JSON body).
|
|
78
|
+
try {
|
|
79
|
+
const Retell = require("retell-sdk").Retell || require("retell-sdk").default;
|
|
80
|
+
const sig = req.headers["x-retell-signature"];
|
|
81
|
+
const ok = Retell.verify(JSON.stringify(req.body), process.env.RETELL_API_KEY, sig);
|
|
82
|
+
if (!ok) return res.status(401).end("invalid signature");
|
|
83
|
+
} catch (e) {
|
|
84
|
+
// If verification lib/shape changes, fail closed unless explicitly bypassed.
|
|
85
|
+
if (process.env.WEBHOOK_SKIP_VERIFY !== "1") {
|
|
86
|
+
return res.status(401).end("signature verification failed: " + e.message);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const { event, call } = req.body || {};
|
|
91
|
+
// Only notify on the fully-analyzed event (has call_analysis).
|
|
92
|
+
if (event !== "call_analyzed") return res.status(200).json({ skipped: event });
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const { subject, html } = buildEmail(call);
|
|
96
|
+
const resend = new Resend(process.env.RESEND_API_KEY);
|
|
97
|
+
await resend.emails.send({
|
|
98
|
+
from: process.env.EMAIL_FROM,
|
|
99
|
+
to: process.env.EMAIL_TO,
|
|
100
|
+
subject,
|
|
101
|
+
html,
|
|
102
|
+
});
|
|
103
|
+
return res.status(200).json({ emailed: true });
|
|
104
|
+
} catch (e) {
|
|
105
|
+
// Return 200 so Retell doesn't retry forever on a mail error; log for visibility.
|
|
106
|
+
console.error("email send failed:", e.message);
|
|
107
|
+
return res.status(200).json({ emailed: false, error: e.message });
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
module.exports.buildEmail = buildEmail;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "virtuphil-webhook",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Retell webhook -> Resend email notifier (serverless)",
|
|
5
|
+
"main": "api/retell-webhook.js",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"resend": "^3.0.0",
|
|
8
|
+
"retell-sdk": "^4.0.0"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18"
|
|
12
|
+
}
|
|
13
|
+
}
|