callwright 1.0.2 → 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 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
- const facts = (job.principal && job.principal.facts) || {};
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; } }
@@ -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.2",
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",
@@ -52,6 +52,12 @@
52
52
  "x-pii": true,
53
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).",
54
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" }
55
61
  }
56
62
  }
57
63
  },