jsfe 1.10.1 → 1.10.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/dist/forms/flows.d.ts.map +1 -1
- package/dist/forms/flows.js +3 -2
- package/dist/forms/identity-flows.d.ts.map +1 -1
- package/dist/forms/identity-flows.js +6 -2
- package/dist/helpers/form-state.d.ts.map +1 -1
- package/dist/helpers/form-state.js +4 -1
- package/dist/helpers/match-inputs.d.ts.map +1 -1
- package/dist/helpers/match-inputs.js +5 -0
- package/dist/runtime-identity.js +1 -1
- package/docs/copilot-reference.md +3 -1
- package/docs/development-release.md +13 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../../src/forms/flows.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../../src/forms/flows.js"],"names":[],"mappings":"AAsHA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2DC;AACD;;;GAGG;AACH;;;;;;EAGC;AAyYD,wHAAwH;AACxH,wEAaC;AACD,4FAA4F;AAC5F;;;EAYC;AACD,sGAAsG;AACtG;;;EAOC;AA7jBM,6CAAuG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAovB9G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAOG"}
|
package/dist/forms/flows.js
CHANGED
|
@@ -70,7 +70,8 @@ export const FIELD_PRESETS = {
|
|
|
70
70
|
question: 'Please say or enter your card number.', question_es: 'Por favor diga o ingrese el número de su tarjeta.',
|
|
71
71
|
retry: 'Please say or enter the full card number.', retry_es: 'Por favor diga o ingrese el número completo de su tarjeta.' },
|
|
72
72
|
card_expiration: { label: 'expiration date', label_es: 'fecha de vencimiento',
|
|
73
|
-
|
|
73
|
+
// MM0YY: a zero said between month and year ("020 28", prod 2026-10-25 14:10, was taken for the billing ZIP)
|
|
74
|
+
profile: { kind: 'pattern', regex: '\\b(0?[1-9]|1[0-2])\\s*(?:/|-|\\s|de)?\\s*(?:20)?(\\d{2})\\b|\\b0[1-9]0\\s+(?:20)?\\d{2}\\b' }, digits: { min: 3, max: 6 },
|
|
74
75
|
validator: 'validate-card-expiration',
|
|
75
76
|
question: 'What is the expiration date, month and year?', question_es: '¿Cuál es la fecha de vencimiento, mes y año?',
|
|
76
77
|
retry: 'Please say or enter the expiration as four digits, month then year, like 0 9 2 8.', retry_es: 'Por favor diga o ingrese el vencimiento en cuatro dígitos, mes y año, por ejemplo 0 9 2 8.' },
|
|
@@ -537,7 +538,7 @@ const validators = [
|
|
|
537
538
|
{ id: 'validate-card-expiration', name: 'ValidateCardExpiration', version: '1.0.0', scope: 'isolated',
|
|
538
539
|
description: 'Validator: month and year, a real month, not expired (validateExpiration). Normalizes to MMYY. Params: input, value.',
|
|
539
540
|
parameters: [{ name: 'input', type: 'string', description: 'input name' }, { name: 'value', type: 'string', description: 'the reading' }],
|
|
540
|
-
steps: [SIDE('vce_check', "(function (v) { var m = /^(0?[1-9]|1[0-2])\\D*(?:20)?(\\d{2})$/.exec(String(v).replace(/\\s+/g, '').replace(/de/gi, '')) || /^(\\d{2})(\\d{2})$/.exec(String(v).replace(/\\D/g, '')); var mm = m ? ('0' + m[1]).slice(-2) : ''; var yy = m ? m[2] : ''; cargo.inputCheck = m && validateExpiration(mm, yy) ? { ok: true, value: mm + yy } : { ok: false, message: \"That expiration date doesn't look right.\", message_es: 'Esa fecha de vencimiento no parece correcta.' }; return true; })(value)")] },
|
|
541
|
+
steps: [SIDE('vce_check', "(function (v) { var m = /^(0?[1-9]|1[0-2])\\D*(?:20)?(\\d{2})$/.exec(String(v).replace(/\\s+/g, '').replace(/de/gi, '')) || /^(\\d{2})(\\d{2})$/.exec(String(v).replace(/\\D/g, '')) || /^(0[1-9])0(\\d{2})$/.exec(String(v).replace(/\\D/g, '')); var mm = m ? ('0' + m[1]).slice(-2) : ''; var yy = m ? m[2] : ''; cargo.inputCheck = m && validateExpiration(mm, yy) ? { ok: true, value: mm + yy } : { ok: false, message: \"That expiration date doesn't look right.\", message_es: 'Esa fecha de vencimiento no parece correcta.' }; return true; })(value)")] },
|
|
541
542
|
];
|
|
542
543
|
// ── field collectors and sub-forms ──────────────────────────────────────────────────────────────────────────────────
|
|
543
544
|
const fieldCollectors = Object.entries(FIELD_COLLECTORS).map(([id, field]) => formFlow({
|
|
@@ -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":"form-state.d.ts","sourceRoot":"","sources":["../../src/helpers/form-state.js"],"names":[],"mappings":"AAQA,qDAKC;AAED,oDAEC;AAED,qFAAqF;AACrF,oEA6BC;AAuCD,2FAEC;AACD,8FAcC;AACD,2DAOC;AACD,
|
|
1
|
+
{"version":3,"file":"form-state.d.ts","sourceRoot":"","sources":["../../src/helpers/form-state.js"],"names":[],"mappings":"AAQA,qDAKC;AAED,oDAEC;AAED,qFAAqF;AACrF,oEA6BC;AAuCD,2FAEC;AACD,8FAcC;AACD,2DAOC;AACD,uEA0CC;AAED,kGAAkG;AAClG,2GAOC;AACD,0HAA0H;AAC1H,uEAwBC;AACD,8HAA8H;AAC9H;;;;;EAWC;AACD,uGAAuG;AACvG;;;;;;;;;;;QAgCC;AAED,0FAA0F;AAC1F;;;SAIC;AACD,gGAAgG;AAChG;;;;;;;;;;;EAQC;AAED,sGAAsG;AACtG,2DAIC;AACD;;;GAGG;AACH;;;;;;;SAkBC;AAED,2FAA2F;AAC3F,oGAIC;AACD,gFA4BC;AAED,2GAA2G;AAC3G;;;EASC;AACD,mEAoBC;AACD,mEAGC;AACD,sEAUC;AACD,+EAEC;AAoCD,qFAYC;AACD,oGAqCC;AACD;;;SAKC;AACD,qEAGC;AACD,gEAgBC;AACD,2DAQC;AACD,oGAUC;AAED,uEAAuE;AACvE,wHA+BC;AAED,uHAAuH;AACvH,6EAaC;AACD,mHAAmH;AACnH,iGAsDC;AACD;;;GAGG;AACH;;;SAcC;AACD,+EASC;AACD,8EAeC;AAnoBM,8CAAmE"}
|
|
@@ -190,7 +190,10 @@ export function formInputChain(forms, id, cargo = {}) {
|
|
|
190
190
|
out.push({ ...input,
|
|
191
191
|
...(prefix ? { label: `${prefix}: ${input.label || input.name}`, label_es: `${prefixEs || prefix}: ${input.label_es || input.label || input.name}` } : {}),
|
|
192
192
|
...(input.choices_from && Array.isArray(state.data?.[input.choices_from]) ? { profile: { ...input.profile, choices: state.data[input.choices_from] } } : {}),
|
|
193
|
-
...(activation ? { activation } : {}), form: target, scope, ...(spec.any_of ? { group: spec.name, exclusive: spec.exclusive === true } : {}), value: state.values[input.name]
|
|
193
|
+
...(activation ? { activation } : {}), form: target, scope, ...(spec.any_of ? { group: spec.name, exclusive: spec.exclusive === true } : {}), value: state.values[input.name],
|
|
194
|
+
// its declared prerequisites hold accepted values: a field that cannot be validated yet is not a context for
|
|
195
|
+
// an unnamed out-of-order answer (contract, reference §5)
|
|
196
|
+
ready: (input.depends_on || spec.depends_on || []).every(name => hasFormValue(state.values[name])) });
|
|
194
197
|
}
|
|
195
198
|
}
|
|
196
199
|
};
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;EAoLC"}
|
|
@@ -353,6 +353,11 @@ export function matchInputs(answer, candidates, current, choiceKey) {
|
|
|
353
353
|
else { /* the current input */ }
|
|
354
354
|
return true;
|
|
355
355
|
}
|
|
356
|
+
else if (bare(reading) && fieldOf(reading).ready === false) {
|
|
357
|
+
// a bare number for a field whose prerequisites are not filled yet (the sub-account before the account is known)
|
|
358
|
+
refused.push({ form: reading.form, name: reading.name, value: reading.value, rule: 'prerequisite_unmet', spans: reading.spans });
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
356
361
|
else if (bare(reading) && fieldOf(reading).profile.kind === 'amount') {
|
|
357
362
|
refused.push({ form: reading.form, name: reading.name, value: reading.value, rule: 'bare_amount_out_of_order', spans: reading.spans });
|
|
358
363
|
return false;
|
package/dist/runtime-identity.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const JSFE_RUNTIME_FINGERPRINT = "sha256:
|
|
1
|
+
export const JSFE_RUNTIME_FINGERPRINT = "sha256:f1c20de527015068a3315bbb280a647bf9ee7025b781e52500e27ac7677e3787";
|
|
@@ -216,7 +216,9 @@ Rules, in order:
|
|
|
216
216
|
invites. A match is that answer.
|
|
217
217
|
2. **A confirmation is answered only when it is asked.** Out of order it never makes sense, so no words, YES, NO or the
|
|
218
218
|
option's own phrases, fill a confirmation that is not the current question.
|
|
219
|
-
3. **Out of order requires the whole signature.** A
|
|
219
|
+
3. **Out of order requires a ready field and the whole signature.** A bare number never fills, out of order, a field whose
|
|
220
|
+
`depends_on` prerequisites are not filled yet (the sub-account before the account is known); a named answer ("50
|
|
221
|
+
dollars") still waits as a pending value. **The whole signature:** A digit field takes a number only when the whole number fits its
|
|
220
222
|
length; a pattern only when it covers the whole number; a choice only its declared or prepared values. A fragment of
|
|
221
223
|
a longer number (the last four of "88 91 7112") fills nothing.
|
|
222
224
|
4. **A bare number** (no money mark, no word naming a field) is an amount only at a question that is, invites or is
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## 1.10.3: a field is ready before an unnamed answer fills it
|
|
2
|
+
|
|
3
|
+
A bare number never fills, out of order, a field whose `depends_on` prerequisites are not filled yet (`formInputChain`
|
|
4
|
+
marks each candidate `ready`; refusal rule `prerequisite_unmet`): an account number keyed in pieces is no longer taken for
|
|
5
|
+
the payment's sub-account before the account is known. A named answer still waits as a pending value. The shared card
|
|
6
|
+
expiration also reads a zero said between month and year ("020 28" as 02/28).
|
|
7
|
+
|
|
8
|
+
## 1.10.2: "account number" at the contact question
|
|
9
|
+
|
|
10
|
+
At the shared contact question (`get-cell-or-email`), a caller who names the account number without giving it yet ("número
|
|
11
|
+
de cuenta", "account number"; only where the tenant takes account numbers) hears the question again, acknowledged ("Claro,
|
|
12
|
+
su número de cuenta."), instead of the failure menu, so the number keyed next is read as the account number.
|
|
13
|
+
|
|
1
14
|
## 1.10.1: refusals that stopped something
|
|
2
15
|
|
|
3
16
|
`matchInputs` lists a refused reading only when it stopped something: one whose part of the answer a structured in-order
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsfe",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.3",
|
|
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",
|