jsfe 1.10.0 → 1.10.2
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity-flows.d.ts","sourceRoot":"","sources":["../../src/forms/identity-flows.js"],"names":[],"mappings":"AA2BA,+GAA+G;AAC/G,oFAOC;AAQD,wFAAwF;AACxF,iDAGC;AACD,6CAMC;AACD,qHAQC;AAMD,4EAKC;
|
|
1
|
+
{"version":3,"file":"identity-flows.d.ts","sourceRoot":"","sources":["../../src/forms/identity-flows.js"],"names":[],"mappings":"AA2BA,+GAA+G;AAC/G,oFAOC;AAQD,wFAAwF;AACxF,iDAGC;AACD,6CAMC;AACD,qHAQC;AAMD,4EAKC;AAoFD,4DAKC;AAiFD,8EAGC;AA4BD,4FAA4F;AAC5F;;;;EAOC;AAQD,0BAAmI"}
|
|
@@ -86,7 +86,7 @@ export function contactError(kind, options, es = false) {
|
|
|
86
86
|
return es ? 'Lo siento, necesito el teléfono o correo electrónico asociado con su cuenta' + (account ? ', o ' + account : '') + ' para continuar.' : 'Sorry, I do need the phone or email associated with your account' + (account ? ', or ' + account : '') + ' to proceed.';
|
|
87
87
|
}
|
|
88
88
|
const contact = worker('get-cell-or-email', 'GetCellOrEmail', 'Identify a supplied contact or account number; collect missing data through a FORM, with caller-ID eligibility and bounded contact validation.', {
|
|
89
|
-
parameters: contactParams, variables: vars({ ...contactDefaults, contact_profiles_base: contactProfiles }), contract: { role: 'collector', summary: 'Contact identification with FORM ancestry', result: { paths: ['cell_number', 'email', 'acct_number', 'cargo.contactKind', 'cargo.contactValue'], type: 'object' } },
|
|
89
|
+
parameters: contactParams, variables: vars({ ...contactDefaults, contact_profiles_base: contactProfiles, contact_named_options: null, contact_names_account: ['número de cuenta', 'numero de cuenta', 'mi número de cuenta', 'mi numero de cuenta', 'con mi número de cuenta', 'con mi numero de cuenta', 'con el número de cuenta', 'con el numero de cuenta', 'account number', 'my account number', 'with my account number', 'with the account number'] }), contract: { role: 'collector', summary: 'Contact identification with FORM ancestry', result: { paths: ['cell_number', 'email', 'acct_number', 'cargo.contactKind', 'cargo.contactValue'], type: 'object' } },
|
|
90
90
|
steps: [
|
|
91
91
|
set('identity_contact_options', 'contact_options', "contact_options || (retrying && cargo.contactOptions ? Object.assign({},cargo.contactOptions,{fromMenu:true,intro:'',intro_es:''}) : {retry_flow:retry_flow || 'authenticate-user',cancel_flow:cancel_flow || 'contact-support',account_pattern:account_pattern,account_name:account_name,account_name_es:account_name_es,account_shape:account_shape,intro:intro,intro_es:intro_es,caller_on_file:caller_on_file,retry_call_type:retry_call_type || 'reboot',validate_flow:validate_flow,merge_partials:merge_partials === true || merge_partials === 'true',parent_form:parent_form})"),
|
|
92
92
|
effect('identity_contact_remember', '(cargo.contactOptions=contact_options,cargo.authCapture=[{variable:"cell_number",reader:"phone"},{variable:"email",reader:"email"}].concat(contact_options.account_pattern?[{variable:"acct_number",regex:contact_options.account_pattern,normalizer:"[^0-9]"}]:[]),true)'),
|
|
@@ -111,12 +111,16 @@ const contact = worker('get-cell-or-email', 'GetCellOrEmail', 'Identify a suppli
|
|
|
111
111
|
when('identity_contact_split_overshoot', "contact_partial && contact_digits.length > (contact_options.account_shape || []).reduce(function(max,s){return Math.max(max,Math.max.apply(null,s.lengths || [0]));},0)", call('identity_contact_restart_split', 'contact-split-failed', { contact_options: '{{contact_options}}' }, 'replace')),
|
|
112
112
|
set('identity_contact_identifier', 'contact_id', "contact_options.account_pattern ? readIdentifier(contact_text,contact_options.account_pattern) : null"),
|
|
113
113
|
set('identity_contact_phone', 'contact_phone', "readPhone(contact_text) || (validatePhone(contact_digits) ? contact_digits : '')"), set('identity_contact_email', 'contact_mail', 'readEmail(contact_text)'),
|
|
114
|
-
set('identity_contact_classify', 'contact_kind', "contact_id && contact_id.kind === 'account' ? 'account' : contact_phone ? 'phone' : contact_mail ? 'email' : readInternationalPhone(contact_text) ? 'international' : isAgentRequest(contact_text) ? 'agent' : isAnswer(contact_text,'exit') && contact_text.trim().split(/\\s+/).length <= 2 ? 'exit' : contact_allowed && /^(?:no|nope|nah)\\b/i.test(contact_text.trim()) && !/[0-9@]/.test(contact_text) ? 'decline' : contact_allowed && cargo.serviceAnswer && cargo.serviceAnswer.values.caller_decision === 'yes' ? 'caller' : contact_digits ? 'invalid_phone' : contact_text.trim().split(/\\s+/).length>=3 && !/[0-9@]/.test(contact_text) ? 'unmapped' : 'invalid'"),
|
|
114
|
+
set('identity_contact_classify', 'contact_kind', "contact_id && contact_id.kind === 'account' ? 'account' : contact_phone ? 'phone' : contact_mail ? 'email' : contact_options.account_pattern && !contact_digits && matchesChoice(contact_text,contact_names_account) ? 'names_account' : readInternationalPhone(contact_text) ? 'international' : isAgentRequest(contact_text) ? 'agent' : isAnswer(contact_text,'exit') && contact_text.trim().split(/\\s+/).length <= 2 ? 'exit' : contact_allowed && /^(?:no|nope|nah)\\b/i.test(contact_text.trim()) && !/[0-9@]/.test(contact_text) ? 'decline' : contact_allowed && cargo.serviceAnswer && cargo.serviceAnswer.values.caller_decision === 'yes' ? 'caller' : contact_digits ? 'invalid_phone' : contact_text.trim().split(/\\s+/).length>=3 && !/[0-9@]/.test(contact_text) ? 'unmapped' : 'invalid'"),
|
|
115
|
+
set('identity_contact_named', 'contact_named_options', "Object.assign({},contact_options,{fromMenu:false,intro:'Sure, your account number.',intro_es:'Claro, su número de cuenta.'})"),
|
|
115
116
|
set('identity_contact_error', 'contact_error', expression(contactError, 'contact_kind', 'contact_options')), set('identity_contact_error_es', 'contact_error_es', expression(contactError, 'contact_kind', 'contact_options', 'true')),
|
|
116
117
|
cases('identity_contact_route', {
|
|
117
118
|
"condition: contact_kind === 'agent'": call('identity_contact_agent', 'live-agent-requested', undefined, 'reboot'),
|
|
118
119
|
"condition: contact_kind === 'exit'": call('identity_contact_exit', '{{contact_options.cancel_flow}}', undefined, 'reboot'),
|
|
119
120
|
"condition: contact_kind === 'decline'": call('identity_contact_declined', 'contact-caller-declined', { contact_options: '{{contact_options}}' }, 'replace'),
|
|
121
|
+
// The caller names the account number without giving it yet (prod 2026-10-25 13:58: "número de cuenta" got the
|
|
122
|
+
// failure menu, and the number keyed next was taken for other fields): the question again, acknowledged.
|
|
123
|
+
"condition: contact_kind === 'names_account'": contactReplay('identity_contact_names_account', { contact_options: '{{contact_named_options}}' }),
|
|
120
124
|
"condition: contact_kind === 'international'": contactMenu('identity_contact_international'),
|
|
121
125
|
"condition: contact_kind === 'invalid_phone'": contactMenu('identity_contact_invalid_phone'),
|
|
122
126
|
"condition: contact_kind === 'unmapped'": { id: 'identity_contact_off_topic', type: 'DISPATCH', onResponse: 'continue', responseContext: 'account identifier: phone number, email address or account number (teléfono, correo o número de cuenta)', outcome: 'unresolved', reason: 'auth_prompt_off_topic' },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"match-inputs.d.ts","sourceRoot":"","sources":["../../src/helpers/match-inputs.js"],"names":[],"mappings":"AAiBA;;;;GAIG;AACH,sEAQC;AAqED;;;GAGG;AACH,sFAwGC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;mHACmH;AACnH;;;;;;;;;;;;;;;;;;;;;EAeC;AACD;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"match-inputs.d.ts","sourceRoot":"","sources":["../../src/helpers/match-inputs.js"],"names":[],"mappings":"AAiBA;;;;GAIG;AACH,sEAQC;AAqED;;;GAGG;AACH,sFAwGC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;mHACmH;AACnH;;;;;;;;;;;;;;;;;;;;;EAeC;AACD;;;;;;;;;;;;;;;;;;;;;;EAgLC"}
|
|
@@ -303,8 +303,9 @@ export function matchInputs(answer, candidates, current, choiceKey) {
|
|
|
303
303
|
// Rule 2: a confirmation is answered only when asked, firm by CONFIRM_WORDS, soft by the wider YES vocabulary.
|
|
304
304
|
const confirmation = ['firm', 'soft'].includes(c.confirmation) ? c.confirmation : '';
|
|
305
305
|
if (confirmation && !isCur(c)) {
|
|
306
|
-
|
|
307
|
-
|
|
306
|
+
const would = readInputProfile(c.profile, raw, false);
|
|
307
|
+
if (would)
|
|
308
|
+
refused.push({ form: c.form, name: c.name, rule: 'confirmation_out_of_order', spans: (would.alternatives || [would]).flatMap((x) => x.spans || []) });
|
|
308
309
|
continue;
|
|
309
310
|
}
|
|
310
311
|
else {
|
|
@@ -353,11 +354,11 @@ export function matchInputs(answer, candidates, current, choiceKey) {
|
|
|
353
354
|
return true;
|
|
354
355
|
}
|
|
355
356
|
else if (bare(reading) && fieldOf(reading).profile.kind === 'amount') {
|
|
356
|
-
refused.push({ form: reading.form, name: reading.name, value: reading.value, rule: 'bare_amount_out_of_order' });
|
|
357
|
+
refused.push({ form: reading.form, name: reading.name, value: reading.value, rule: 'bare_amount_out_of_order', spans: reading.spans });
|
|
357
358
|
return false;
|
|
358
359
|
}
|
|
359
360
|
else if (/\d/.test(spanText(reading)) && !wholeRuns(reading)) {
|
|
360
|
-
refused.push({ form: reading.form, name: reading.name, value: reading.value, rule: 'fragment' });
|
|
361
|
+
refused.push({ form: reading.form, name: reading.name, value: reading.value, rule: 'fragment', spans: reading.spans });
|
|
361
362
|
return false;
|
|
362
363
|
}
|
|
363
364
|
else {
|
|
@@ -454,7 +455,12 @@ export function matchInputs(answer, candidates, current, choiceKey) {
|
|
|
454
455
|
const clash = (a, b) => (a.spans || []).some(([s, e]) => (b.spans || []).some(([t, u]) => s < u && t < e));
|
|
455
456
|
const kept = taken.filter((a) => !taken.some((b) => b !== a && clash(a, b)));
|
|
456
457
|
for (const r of taken.filter((a) => !kept.includes(a)))
|
|
457
|
-
refused.push({ form: r.form, name: r.name, value: r.value, rule: 'one_part_two_fields' });
|
|
458
|
+
refused.push({ form: r.form, name: r.name, value: r.value, rule: 'one_part_two_fields', spans: r.spans });
|
|
459
|
+
// A refusal is recorded only when it stopped something: a reading whose part of the answer a structured in-order
|
|
460
|
+
// reading (a key, the code, the amount asked) also claimed would have lost anyway ("2" at PAY or LINK is the key, not
|
|
461
|
+
// an amount refused). A free-text fallback claims everything and does not count.
|
|
462
|
+
const claimed = readings.filter((r) => inOrder(r) && !r.fallback).flatMap((r) => r.spans || []);
|
|
463
|
+
const stopped = refused.filter((x) => !(x.spans || []).some(([s, e]) => claimed.some(([t, u]) => s < u && t < e))).map(({ spans, ...x }) => x);
|
|
458
464
|
// Several fields from one answer: those filled out of order are a compound answer.
|
|
459
465
|
if (kept.length > 1)
|
|
460
466
|
kept.forEach((r) => { if (r.rule === 'out_of_order')
|
|
@@ -462,5 +468,5 @@ export function matchInputs(answer, candidates, current, choiceKey) {
|
|
|
462
468
|
hits.push(...kept.map(({ rule, ...r }) => strip(r)));
|
|
463
469
|
// Why each field was filled other than as the current answer, kept beside the hits so their shape never changes.
|
|
464
470
|
const decisions = kept.filter((r) => r.rule).map((r) => ({ form: r.form, name: r.name, rule: r.rule }));
|
|
465
|
-
return { hits, ambiguous, ...(remaining.length ? { remaining } : {}), ...(
|
|
471
|
+
return { hits, ambiguous, ...(remaining.length ? { remaining } : {}), ...(stopped.length ? { refused: stopped } : {}), ...(decisions.length ? { decisions } : {}) };
|
|
466
472
|
}
|
package/dist/runtime-identity.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const JSFE_RUNTIME_FINGERPRINT = "sha256:
|
|
1
|
+
export const JSFE_RUNTIME_FINGERPRINT = "sha256:bf44b0f20dbe41d3ea672662bd6e580e65376ec6aa62c922af8ff037c2663006";
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## 1.10.2: "account number" at the contact question
|
|
2
|
+
|
|
3
|
+
At the shared contact question (`get-cell-or-email`), a caller who names the account number without giving it yet ("número
|
|
4
|
+
de cuenta", "account number"; only where the tenant takes account numbers) hears the question again, acknowledged ("Claro,
|
|
5
|
+
su número de cuenta."), instead of the failure menu, so the number keyed next is read as the account number.
|
|
6
|
+
|
|
7
|
+
## 1.10.1: refusals that stopped something
|
|
8
|
+
|
|
9
|
+
`matchInputs` lists a refused reading only when it stopped something: one whose part of the answer a structured in-order
|
|
10
|
+
reading (a key, the code, the amount asked) also claimed would have lost anyway, so "2" at a menu is its key, not a refused
|
|
11
|
+
amount, and the host's `form_acceptance` counts real leaks. A free-text fallback does not count as a claim. The
|
|
12
|
+
standard-functions test recognizes named function expressions and arrow functions a flow defines for itself.
|
|
13
|
+
|
|
1
14
|
## 1.10.0: context-aware acceptance
|
|
2
15
|
|
|
3
16
|
An answer given out of order fills another question's field only where it is relevant there (reference §5): a confirmation
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsfe",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.2",
|
|
4
4
|
"description": "TypeScript workflow engine for conversational/automation apps: multi-flow orchestration, interrupt/resume, stack-based control, robust errors, and flexible tool/REST integration.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ron Pinkas",
|