callwright 1.0.1 → 1.0.3
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/dispatch-core.js +27 -3
- package/generic-prompt.ja.md +3 -0
- package/generic-prompt.md +4 -1
- package/package.json +1 -1
- package/place_call.schema.json +34 -17
- package/server.js +19 -0
package/dispatch-core.js
CHANGED
|
@@ -119,7 +119,7 @@ function deriveWindows(req, flexMin) {
|
|
|
119
119
|
// `lang` selects the per-language PHRASE block (lang-phrases.json) used for
|
|
120
120
|
// composed helper strings. composeCall has no language conditionals — phrasing
|
|
121
121
|
// is data; this function only injects values.
|
|
122
|
-
function composeCall(job, { lang = "en" } = {}) {
|
|
122
|
+
function composeCall(job, { lang = "en", config = loadConfig() } = {}) {
|
|
123
123
|
const LANG = String(lang || "en").toLowerCase();
|
|
124
124
|
const P = phrasesFor(LANG);
|
|
125
125
|
const profile = loadProfileFor(job.call_type);
|
|
@@ -131,7 +131,31 @@ function composeCall(job, { lang = "en" } = {}) {
|
|
|
131
131
|
.join(" ");
|
|
132
132
|
|
|
133
133
|
const details = job.scenario_details || {};
|
|
134
|
-
|
|
134
|
+
// Resolve stored facts into the per-call "share-if-asked" set. The store
|
|
135
|
+
// (config.principal.facts) holds durable PII; a call attaches ONLY the subset
|
|
136
|
+
// this scenario needs — data minimization — so the raw values never pass
|
|
137
|
+
// through the calling model. Two eligibility sources:
|
|
138
|
+
// - the matched profile's recommended_details keys (auto-resolved), and
|
|
139
|
+
// - keys the caller explicitly names in principal.facts_from_store (opt-in
|
|
140
|
+
// escape hatch for a non-recommended stored key).
|
|
141
|
+
// An explicit principal.facts value always wins over the store (no override,
|
|
142
|
+
// no double-attach); empty stored values are treated as absent.
|
|
143
|
+
const jobFacts = (job.principal && job.principal.facts) || {};
|
|
144
|
+
const storedFacts = (config && config.principal && config.principal.facts) || {};
|
|
145
|
+
const requestedFromStore = Array.isArray(job.principal && job.principal.facts_from_store)
|
|
146
|
+
? job.principal.facts_from_store
|
|
147
|
+
: [];
|
|
148
|
+
const resolveKeys = new Set([
|
|
149
|
+
...(profile?.recommended_details ? Object.keys(profile.recommended_details) : []),
|
|
150
|
+
...requestedFromStore,
|
|
151
|
+
]);
|
|
152
|
+
const resolvedFromStore = {};
|
|
153
|
+
for (const key of resolveKeys) {
|
|
154
|
+
if (jobFacts[key] != null && jobFacts[key] !== "") continue; // caller value wins
|
|
155
|
+
const v = storedFacts[key];
|
|
156
|
+
if (v != null && v !== "") resolvedFromStore[key] = v;
|
|
157
|
+
}
|
|
158
|
+
const facts = { ...resolvedFromStore, ...jobFacts };
|
|
135
159
|
const missingRecommended = [];
|
|
136
160
|
if (profile?.recommended_details) {
|
|
137
161
|
for (const [key, why] of Object.entries(profile.recommended_details)) {
|
|
@@ -348,7 +372,7 @@ async function placeCall(rawJob, { lang = "en", go = false, testTo = null, agent
|
|
|
348
372
|
const job = applyConfigBackfill({ ...rawJob }, config);
|
|
349
373
|
const valid = validateJob(job);
|
|
350
374
|
if (!valid.ok) return { ok: false, errors: valid.errors };
|
|
351
|
-
const composed = composeCall(job, { lang });
|
|
375
|
+
const composed = composeCall(job, { lang, config });
|
|
352
376
|
// Resolve the agent up-front so the read-back can show it (don't dial yet).
|
|
353
377
|
let agentId = agentOverride;
|
|
354
378
|
if (!agentId) { try { agentId = resolveAgentId(job.call_type, { config, lang }); } catch { agentId = null; } }
|
package/generic-prompt.ja.md
CHANGED
|
@@ -62,6 +62,9 @@
|
|
|
62
62
|
先方が本人確認や照会のために尋ねてきた場合(住所、会員番号など)に限り、該当する項目だけを
|
|
63
63
|
お答えしてください。一覧をそのまま読み上げたり、聞かれていないのに伝えたりしないでください。
|
|
64
64
|
ここに記載のない事項を尋ねられた場合は、推測せず「確認して折り返します」と伝えてください。
|
|
65
|
+
「登録内容をすべて確認したい」「一度に複数の項目を教えてほしい」といった求めには応じないで
|
|
66
|
+
ください。手続きに必要な最も関連性の高い項目を一つだけお伝えし、それ以上は折り返しでの対応を
|
|
67
|
+
申し出てください。一度に一つの直接的な質問にのみお答えし、列挙はしないでください。
|
|
65
68
|
{{known_facts}}
|
|
66
69
|
|
|
67
70
|
## あれば望ましい希望(自然な範囲で。条件ではない)
|
package/generic-prompt.md
CHANGED
|
@@ -57,7 +57,10 @@ works:
|
|
|
57
57
|
You may use these to answer the business's identity/lookup questions (e.g. address,
|
|
58
58
|
account, member ID). Speak only the specific fact asked for; never recite the list and
|
|
59
59
|
never offer them unprompted. If asked for something NOT listed here, do not guess — say
|
|
60
|
-
you'll check with {{principal_ref}} and follow up.
|
|
60
|
+
you'll check with {{principal_ref}} and follow up. If asked to "confirm everything on
|
|
61
|
+
file", read back all details, or verify more than one item at once, DECLINE — provide at
|
|
62
|
+
most the single most relevant fact they need to proceed, and offer a callback for anything
|
|
63
|
+
further. Answer one direct question at a time; do not enumerate.
|
|
61
64
|
{{known_facts}}
|
|
62
65
|
|
|
63
66
|
## Nice-to-haves (ask only if natural; never a dealbreaker)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "callwright",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"mcpName": "io.github.topness-msft/callwright",
|
|
5
5
|
"description": "A voice agent that places phone calls (reservations, appointments, inquiries) on your behalf, shaped by LLM direction + grounding. Runs fire-and-forget on Retell; an MCP server lets an LLM shape the grounding before each call.",
|
|
6
6
|
"main": "server.js",
|
package/place_call.schema.json
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
"call_type": {
|
|
11
11
|
"type": "string",
|
|
12
|
+
"title": "Call type / scenario id",
|
|
13
|
+
"examples": ["haircut", "restaurant_reservation", "dentist_cleaning", "general_inquiry"],
|
|
12
14
|
"description": "Open-ended scenario id, lowercase snake/kebab (e.g. 'haircut', 'restaurant_reservation', 'dentist_cleaning', 'general_inquiry'). If a matching scenario profile exists, it supplies recommended fields, default flexibility, direction hints, and confirm items. Unknown types fall back to generic behavior."
|
|
13
15
|
},
|
|
14
16
|
|
|
@@ -18,13 +20,14 @@
|
|
|
18
20
|
"required": ["business_name", "phone_number"],
|
|
19
21
|
"additionalProperties": false,
|
|
20
22
|
"properties": {
|
|
21
|
-
"business_name": { "type": "string", "description": "Name of the business, used by the agent when speaking." },
|
|
23
|
+
"business_name": { "type": "string", "title": "Business name", "description": "Name of the business, used by the agent when speaking." },
|
|
22
24
|
"phone_number": {
|
|
23
25
|
"type": "string",
|
|
24
|
-
"
|
|
26
|
+
"title": "Business phone number (E.164)",
|
|
27
|
+
"examples": ["+15555550199", "+81312345678"],
|
|
25
28
|
"pattern": "^\\+[1-9]\\d{6,14}$"
|
|
26
29
|
},
|
|
27
|
-
"timezone": { "type": "string", "description": "IANA tz of the business, e.g. America/New_York. Infer from area code if not given, then confirm." }
|
|
30
|
+
"timezone": { "type": "string", "title": "Business timezone (IANA)", "examples": ["America/New_York", "Asia/Tokyo"], "description": "IANA tz of the business, e.g. America/New_York. Infer from area code if not given, then confirm." }
|
|
28
31
|
}
|
|
29
32
|
},
|
|
30
33
|
|
|
@@ -34,17 +37,27 @@
|
|
|
34
37
|
"required": [],
|
|
35
38
|
"additionalProperties": false,
|
|
36
39
|
"properties": {
|
|
37
|
-
"name": { "type": "string", "description": "Name to give for the reservation/appointment. OPTIONAL — omit for general inquiries where no booking/identity is involved, so the agent does not attach the principal's name to the call." },
|
|
40
|
+
"name": { "type": "string", "title": "Principal name", "x-pii": true, "description": "Name to give for the reservation/appointment. OPTIONAL — omit for general inquiries where no booking/identity is involved, so the agent does not attach the principal's name to the call." },
|
|
38
41
|
"anonymous": { "type": "boolean", "description": "Set true for a truly nameless call (general inquiries). Suppresses backfill of name/callback_number from config and places the call with NO principal identity. Default false (basics are backfilled for convenience)." },
|
|
39
42
|
"callback_number": {
|
|
40
43
|
"type": "string",
|
|
41
|
-
"
|
|
44
|
+
"title": "Callback number (E.164)",
|
|
45
|
+
"x-pii": true,
|
|
46
|
+
"examples": ["+15555550142"],
|
|
42
47
|
"pattern": "^\\+[1-9]\\d{6,14}$"
|
|
43
48
|
},
|
|
44
49
|
"facts": {
|
|
45
50
|
"type": "object",
|
|
51
|
+
"title": "Per-call PII subset",
|
|
52
|
+
"x-pii": true,
|
|
46
53
|
"description": "MINIMAL per-call subset of standing facts about the principal that the agent may share ONLY IF the business asks (never volunteer). Keys are snake_case labels the agent speaks humanized (e.g. service_address, account_number, date_of_birth, member_id, vehicle, insurance_provider, loyalty_number). Values are strings/numbers/booleans. DATA MINIMIZATION: include ONLY what THIS call_type plausibly needs — do NOT dump the user's full profile. The host LLM selects this subset from the user's standing profile and/or connected context MCPs (WorkIQ, Gmail, calendar, CRM). This is the right home for PII: anything identifying belongs here (only-if-asked + surfaced in the read-back), NOT in scenario_details. Distinct from request.scenario_details (non-PII call specifics).",
|
|
47
54
|
"additionalProperties": { "type": ["string", "number", "boolean"] }
|
|
55
|
+
},
|
|
56
|
+
"facts_from_store": {
|
|
57
|
+
"type": "array",
|
|
58
|
+
"title": "Stored fact keys to resolve at dial time",
|
|
59
|
+
"description": "OPTIONAL escape hatch. Names stored fact keys (from the user's saved profile) to pull into this call's share-if-asked set WITHOUT the value ever passing through you. Profile-recommended keys are auto-resolved from the store already, so use this only to include a stored key that the matched scenario profile does NOT recommend (e.g. member_id on a general_inquiry). Data minimization still applies: name only what THIS call needs. Values you already have go directly in `facts` instead.",
|
|
60
|
+
"items": { "type": "string" }
|
|
48
61
|
}
|
|
49
62
|
}
|
|
50
63
|
},
|
|
@@ -57,10 +70,14 @@
|
|
|
57
70
|
"properties": {
|
|
58
71
|
"summary": {
|
|
59
72
|
"type": "string",
|
|
73
|
+
"title": "Request summary",
|
|
74
|
+
"examples": ["Dinner reservation for 4", "Routine dental cleaning"],
|
|
60
75
|
"description": "One sentence the agent can act on, e.g. 'Dinner reservation for 4'. For appointments, include the service, e.g. 'Routine dental cleaning'."
|
|
61
76
|
},
|
|
62
77
|
"opening_ask": {
|
|
63
78
|
"type": "string",
|
|
79
|
+
"title": "Opening ask (spoken)",
|
|
80
|
+
"examples": ["I'm calling to ask whether you have a table for two this Friday at 6 PM.", "金曜日18時に2名で空席があるか伺えますでしょうか。"],
|
|
64
81
|
"description": "OPTIONAL. The first thing the agent says after disclosing it's an AI — leads with the goal/need, e.g. \"I'm calling to confirm an appointment. Could you help me with that?\". If omitted, one is derived from `summary`. Do NOT phrase as 'is now a good time?'."
|
|
65
82
|
},
|
|
66
83
|
"party_size": { "type": "integer", "minimum": 1, "description": "Required for reservations. Number of people." },
|
|
@@ -71,8 +88,8 @@
|
|
|
71
88
|
"required": ["date", "time"],
|
|
72
89
|
"additionalProperties": false,
|
|
73
90
|
"properties": {
|
|
74
|
-
"date": { "type": "string", "format": "date", "description": "YYYY-MM-DD. Resolve relative dates ('Saturday') against the business timezone and confirm the absolute date." },
|
|
75
|
-
"time": { "type": "string", "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$", "description": "24h HH:MM." }
|
|
91
|
+
"date": { "type": "string", "format": "date", "examples": ["2026-07-10"], "description": "YYYY-MM-DD. Resolve relative dates ('Saturday') against the business timezone and confirm the absolute date." },
|
|
92
|
+
"time": { "type": "string", "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$", "examples": ["19:00"], "description": "24h HH:MM." }
|
|
76
93
|
}
|
|
77
94
|
},
|
|
78
95
|
"acceptable_windows": {
|
|
@@ -142,19 +159,19 @@
|
|
|
142
159
|
"description": "Optional per-call overrides of the direction template defaults. Omit to use template defaults.",
|
|
143
160
|
"additionalProperties": false,
|
|
144
161
|
"properties": {
|
|
145
|
-
"time_flexibility_minutes": { "type": "integer", "minimum": 0 },
|
|
146
|
-
"disclose_ai": { "type": "boolean", "description": "Default true. Do not set false without a legal reason." },
|
|
147
|
-
"record_audio": { "type": "boolean", "description": "Default false. If true, agent must speak a consent line." },
|
|
148
|
-
"max_call_duration_seconds": { "type": "integer", "minimum": 30 },
|
|
149
|
-
"on_voicemail": { "type": "string", "enum": ["leave_message", "hang_up"] },
|
|
162
|
+
"time_flexibility_minutes": { "type": "integer", "minimum": 0, "default": 45, "title": "Time flexibility (minutes)" },
|
|
163
|
+
"disclose_ai": { "type": "boolean", "default": true, "description": "Default true. Do not set false without a legal reason." },
|
|
164
|
+
"record_audio": { "type": "boolean", "default": false, "description": "Default false. If true, agent must speak a consent line." },
|
|
165
|
+
"max_call_duration_seconds": { "type": "integer", "minimum": 30, "title": "Max call duration (seconds)" },
|
|
166
|
+
"on_voicemail": { "type": "string", "enum": ["leave_message", "hang_up"], "default": "leave_message" },
|
|
150
167
|
"retry": {
|
|
151
168
|
"type": "object",
|
|
152
169
|
"description": "Retry-on-no-answer policy. Defaults to exactly 1 retry; never raise max_retries above 1 without an explicit user request (repeated calls are spammy).",
|
|
153
170
|
"additionalProperties": false,
|
|
154
171
|
"properties": {
|
|
155
|
-
"max_retries": { "type": "integer", "minimum": 0, "maximum": 5, "description": "Number of re-dials on no-answer. Default 1. >1 only on explicit user request." },
|
|
156
|
-
"on": { "type": "array", "items": { "type": "string", "enum": ["no_answer", "busy", "failed"] } },
|
|
157
|
-
"delay_seconds": { "type": "integer", "minimum": 0, "maximum": 600 }
|
|
172
|
+
"max_retries": { "type": "integer", "minimum": 0, "maximum": 5, "default": 1, "description": "Number of re-dials on no-answer. Default 1. >1 only on explicit user request." },
|
|
173
|
+
"on": { "type": "array", "default": ["no_answer", "busy", "failed"], "items": { "type": "string", "enum": ["no_answer", "busy", "failed"] } },
|
|
174
|
+
"delay_seconds": { "type": "integer", "minimum": 0, "maximum": 600, "default": 60 }
|
|
158
175
|
}
|
|
159
176
|
}
|
|
160
177
|
}
|
|
@@ -165,8 +182,8 @@
|
|
|
165
182
|
"description": "Optional per-call 'text me when it's done'. Off by default — results are pulled on demand via get_call_outcome. Only set sms:true if the user asks to be notified.",
|
|
166
183
|
"additionalProperties": false,
|
|
167
184
|
"properties": {
|
|
168
|
-
"sms": { "type": "boolean", "description": "True = send the user an SMS when this call completes." },
|
|
169
|
-
"to": { "type": "string", "description": "The user's own number to text (E.164). If omitted, uses the saved notify number; if none, ask the user." }
|
|
185
|
+
"sms": { "type": "boolean", "default": false, "description": "True = send the user an SMS when this call completes." },
|
|
186
|
+
"to": { "type": "string", "title": "Notify number (E.164)", "x-pii": true, "description": "The user's own number to text (E.164). If omitted, uses the saved notify number; if none, ask the user." }
|
|
170
187
|
}
|
|
171
188
|
}
|
|
172
189
|
}
|
package/server.js
CHANGED
|
@@ -117,6 +117,7 @@ function buildServer() {
|
|
|
117
117
|
"get_setup_status",
|
|
118
118
|
{
|
|
119
119
|
title: "Get setup status",
|
|
120
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
120
121
|
description: "Report what callwright has configured (from-number, agent, your name/callback, fact keys) and what's missing. Call this first if you're unsure whether setup is complete; if basics are missing, ask the user for them and call configure.",
|
|
121
122
|
inputSchema: {},
|
|
122
123
|
},
|
|
@@ -127,6 +128,7 @@ function buildServer() {
|
|
|
127
128
|
"configure",
|
|
128
129
|
{
|
|
129
130
|
title: "Configure principal + (optional) provision infra",
|
|
131
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
130
132
|
description: "Save the user's standing profile so calls need less per-call input. Set name and callback_number (the always-safe basics). Optionally save standing facts (durable PII like service_address, member_id — a STORE the LLM later draws minimal subsets from). This is also the SAVE TARGET for the lazy-save nudge: when place_call returns a save_suggestion (durable facts not yet stored), offer to save them and, if the user agrees, call configure with those facts so they're not re-asked next time. Set run_provisioning=true to verify the Retell key, pick the phone number, and create the generic agent if missing. Gather values from the user in chat before calling.",
|
|
131
133
|
inputSchema: {
|
|
132
134
|
name: z.string().optional().describe("Name used when a call becomes a booking."),
|
|
@@ -164,6 +166,7 @@ function buildServer() {
|
|
|
164
166
|
"list_scenarios",
|
|
165
167
|
{
|
|
166
168
|
title: "List scenario profiles",
|
|
169
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
167
170
|
description: "List known scenario profiles (recommended details, default flexibility, aliases). Use to learn what grounding a given call type benefits from before constructing place_call.",
|
|
168
171
|
inputSchema: {},
|
|
169
172
|
},
|
|
@@ -181,6 +184,7 @@ function buildServer() {
|
|
|
181
184
|
"place_call",
|
|
182
185
|
{
|
|
183
186
|
title: "Place an outbound phone call",
|
|
187
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
184
188
|
description:
|
|
185
189
|
"Place a call on the user's behalf. DRY-RUN by default: returns a read-back (including exactly which personal data types will be available to the agent) and does NOT dial. Review it with the user, then call again with confirm:true to actually place the call. DATA MINIMIZATION: in principal.facts include ONLY what THIS call needs; put PII in principal.facts (shared only if asked), not in scenario_details (spoken proactively). For a truly nameless general inquiry set principal.anonymous:true. LANGUAGE: the agent SPEAKS the text fields you provide verbatim. When the call is not in English (set lang accordingly), you MUST write every spoken field in that language — request.summary, request.opening_ask, scenario_details values, preferences, must_confirm, and any constraints. Do NOT write them in English for a non-English call. Fire-and-forget: returns a call_id; read the outcome later with get_call_outcome.",
|
|
186
190
|
inputSchema: {
|
|
@@ -306,6 +310,7 @@ function buildServer() {
|
|
|
306
310
|
"get_call_outcome",
|
|
307
311
|
{
|
|
308
312
|
title: "Get call outcome",
|
|
313
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
309
314
|
description: "Fetch a call's outcome, post-call analysis, and transcript by call_id. Use after place_call (give it ~30-60s to complete + analyze).",
|
|
310
315
|
inputSchema: { call_id: z.string() },
|
|
311
316
|
},
|
|
@@ -321,6 +326,7 @@ function buildServer() {
|
|
|
321
326
|
"list_recent_calls",
|
|
322
327
|
{
|
|
323
328
|
title: "List recent calls",
|
|
329
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
324
330
|
description: "List recent calls in this Retell workspace (most recent first).",
|
|
325
331
|
inputSchema: { limit: z.number().int().min(1).max(50).optional() },
|
|
326
332
|
},
|
|
@@ -341,6 +347,7 @@ function buildServer() {
|
|
|
341
347
|
"list_retries",
|
|
342
348
|
{
|
|
343
349
|
title: "List call retries",
|
|
350
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
344
351
|
description: "Show recent automatic call retries (no-answer/busy/failed re-dials), most recent first. Retries default to at most 1 per call and never auto-escalate. Each entry shows the original call, the attempt number/cap, the new call_id (if placed), and status (scheduled/placed/failed).",
|
|
345
352
|
inputSchema: { limit: z.number().int().min(1).max(100).optional() },
|
|
346
353
|
},
|
|
@@ -354,6 +361,7 @@ function buildServer() {
|
|
|
354
361
|
"configure_notifications",
|
|
355
362
|
{
|
|
356
363
|
title: "Configure SMS notifications",
|
|
364
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
357
365
|
description: "Set up the optional 'text me when the call is done' feature. Saves the user's mobile number as the default notify target, provisions the one-time SMS summary agent if needed, and (with test:true) sends a test SMS now so the user can confirm delivery. Per-call SMS is still opt-in via place_call notify.sms — this just stores the number + infra. NOTE: the Retell from-number must be SMS-capable (KYC-gated).",
|
|
358
366
|
inputSchema: {
|
|
359
367
|
sms_to: z.string().optional().describe("The user's mobile number (E.164) to text by default."),
|
|
@@ -403,6 +411,7 @@ function buildServer() {
|
|
|
403
411
|
"learn_from_call",
|
|
404
412
|
{
|
|
405
413
|
title: "Learn from a call",
|
|
414
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
406
415
|
description: "Improve the system from a completed call. If the call_type matches a profile, it's ENRICHED (deferred questions become recommended_details, value-baked ones rejected). If it has NO profile, the (generalized) scenario is STAGED as a candidate; once seen >=2 times it becomes a PROPOSAL you can review and create via create_profile. Creation is never silent — it's proposed for you to approve (or merge into an existing profile with add_scenario_alias).",
|
|
407
416
|
inputSchema: {
|
|
408
417
|
call_id: z.string(),
|
|
@@ -447,6 +456,7 @@ function buildServer() {
|
|
|
447
456
|
"list_candidates",
|
|
448
457
|
{
|
|
449
458
|
title: "List scenario candidates",
|
|
459
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
450
460
|
description: "Show staged scenario candidates (unmatched call types seen but not yet a profile) with their occurrence counts, variant names, and the questions agents had to defer. Candidates seen >=2 times are 'ready' to propose as a new profile (create_profile) or merge into an existing one (add_scenario_alias).",
|
|
451
461
|
inputSchema: {},
|
|
452
462
|
},
|
|
@@ -469,6 +479,7 @@ function buildServer() {
|
|
|
469
479
|
"create_profile",
|
|
470
480
|
{
|
|
471
481
|
title: "Create a scenario profile",
|
|
482
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
472
483
|
description: "Create a NEW scenario profile (propose-and-confirm — call this only after reviewing a learn_from_call proposal with the user). GUARDS: the name must be a GENERAL scenario (not instance-shaped like 'dinner_for_2'); recommended_details must be GENERALIZED field keys ('destination', not 'destination_haneda') — values belong in the per-call job, never the profile. If the scenario is really the same as an existing profile, use add_scenario_alias instead. Clears the matching candidate on success.",
|
|
473
484
|
inputSchema: {
|
|
474
485
|
name: z.string().describe("snake_case general scenario id, e.g. 'hotel_transport_inquiry'."),
|
|
@@ -504,6 +515,7 @@ function buildServer() {
|
|
|
504
515
|
"add_scenario_alias",
|
|
505
516
|
{
|
|
506
517
|
title: "Add an alias to a scenario profile",
|
|
518
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
507
519
|
description: "Merge a call_type into an EXISTING profile by adding it as an alias (the anti-splintering 'merge, don't create' path). Use when a candidate / new call_type is really the same scenario as a profile you already have. Clears the matching candidate.",
|
|
508
520
|
inputSchema: {
|
|
509
521
|
profile: z.string().describe("The existing profile to merge into (e.g. 'hotel_transport_inquiry')."),
|
|
@@ -532,6 +544,7 @@ function buildServer() {
|
|
|
532
544
|
"reject_candidate",
|
|
533
545
|
{
|
|
534
546
|
title: "Reject a scenario candidate",
|
|
547
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
535
548
|
description: "Discard a staged scenario candidate that is noise or a one-off (not a real recurring scenario). Removes it from the candidates store.",
|
|
536
549
|
inputSchema: { scenario: z.string().describe("The candidate scenario name to drop (from list_candidates).") },
|
|
537
550
|
},
|
|
@@ -549,6 +562,7 @@ function buildServer() {
|
|
|
549
562
|
"list_voices",
|
|
550
563
|
{
|
|
551
564
|
title: "List Retell voices",
|
|
565
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
552
566
|
description: "List available Retell voices (for picking a voice when adding a language). Optionally filter by accent (e.g. 'French', 'Japanese') or a free-text query (name/provider/accent). NOTE: Retell voices have an accent, not a hard language field, and most are multilingual — accent is a hint, not a guarantee. Validate the final choice with a verification call.",
|
|
553
567
|
inputSchema: {
|
|
554
568
|
accent: z.string().optional().describe("Filter by accent substring, e.g. 'French', 'Spanish', 'Japanese'."),
|
|
@@ -567,6 +581,7 @@ function buildServer() {
|
|
|
567
581
|
"list_languages",
|
|
568
582
|
{
|
|
569
583
|
title: "List call languages",
|
|
584
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
570
585
|
description: "List the call languages this server can conduct calls in: the built-in English base plus any added languages (with their agent, voice, Retell language code, and whether prompt/phrase assets are on the volume). Use to see what's available before place_call or add_language.",
|
|
571
586
|
inputSchema: {},
|
|
572
587
|
},
|
|
@@ -577,6 +592,7 @@ function buildServer() {
|
|
|
577
592
|
"add_language",
|
|
578
593
|
{
|
|
579
594
|
title: "Add a call language",
|
|
595
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
580
596
|
description: "Add a NEW call language at runtime (no redeploy). Two-phase: call with just lang + display_name to get back the base prompt + English phrase block to TRANSLATE; then call again with prompt_text + phrases (translated, preserving every {{variable}} and {placeholder}) to create the language agent. Pick a native-accent voice_id via list_voices (optional — defaults to the English house voice, which you should override for best quality). English cannot be added (it's the built-in base). After it succeeds, run a verification call before production.",
|
|
581
597
|
inputSchema: {
|
|
582
598
|
lang: z.string().regex(/^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/, "BCP-47 code, e.g. 'fr', 'es', 'pt-BR'").describe("BCP-47 language code to add (e.g. 'fr', 'es', 'de', 'pt-BR')."),
|
|
@@ -600,6 +616,7 @@ function buildServer() {
|
|
|
600
616
|
"verify_language",
|
|
601
617
|
{
|
|
602
618
|
title: "Verify a call language",
|
|
619
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
603
620
|
description: "Quality-gate a newly added language before production. DRY-RUN by default: returns a review card with the EXACT spoken opening (greeting + your ask + AI disclosure) and voicemail message — composed by the language's agent — plus a native-review checklist. To HEAR it live, pass to:<your phone> and confirm:true and the agent will call you so you can judge accent, etiquette, and the disclosure by ear. For the truest review, pass target-language sample_summary and sample_opening_ask.",
|
|
604
621
|
inputSchema: {
|
|
605
622
|
lang: z.string().describe("The registered language code to verify (e.g. 'fr')."),
|
|
@@ -663,6 +680,7 @@ function buildServer() {
|
|
|
663
680
|
"update_language",
|
|
664
681
|
{
|
|
665
682
|
title: "Update a call language",
|
|
683
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
666
684
|
description: "Change an already-added language: replace its prompt_text and/or phrases (re-translated), and/or change its voice_id, Retell language code, interruption_sensitivity, or stt_mode. Only the provided fields change. Use list_languages to see what's registered.",
|
|
667
685
|
inputSchema: {
|
|
668
686
|
lang: z.string().describe("The registered language code to update (e.g. 'fr')."),
|
|
@@ -685,6 +703,7 @@ function buildServer() {
|
|
|
685
703
|
"remove_language",
|
|
686
704
|
{
|
|
687
705
|
title: "Remove a call language",
|
|
706
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
688
707
|
description: "Unregister an added language and delete its volume prompt + phrase assets. By default the Retell agent is LEFT in your dashboard (set delete_agent:true to delete it too). English cannot be removed.",
|
|
689
708
|
inputSchema: {
|
|
690
709
|
lang: z.string().describe("The registered language code to remove (e.g. 'fr')."),
|