jsfe 1.9.3 → 1.10.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/dist/forms/contracts.d.ts +1 -0
- package/dist/forms/contracts.d.ts.map +1 -1
- package/dist/forms/flows.d.ts +1 -7
- package/dist/forms/flows.d.ts.map +1 -1
- package/dist/forms/flows.js +29 -6
- package/dist/forms/identity-flows.d.ts.map +1 -1
- package/dist/forms/identity-flows.js +4 -2
- package/dist/helpers/index.d.ts +3 -1
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/index.js +3 -3
- package/dist/helpers/match-inputs.d.ts +38 -0
- package/dist/helpers/match-inputs.d.ts.map +1 -1
- package/dist/helpers/match-inputs.js +92 -4
- package/dist/runtime-identity.js +1 -1
- package/docs/copilot-reference.md +38 -0
- package/docs/development-release.md +17 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../../src/forms/contracts.js"],"names":[],"mappings":"AAgBA;;;IAGC;AACD;;;;;EAGC;AACD,qGAAqG;AACrG;;QAwCC;AAqBD;;;QAEC;AAgBD;;;;;;;;;QASC;AACD;;;;QAGC;AACD;;;;;;;;QA8BC;AAED,mHAAmH;AACnH,kDAgBC;AAED,+GAA+G;AAC/G
|
|
1
|
+
{"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../../src/forms/contracts.js"],"names":[],"mappings":"AAgBA;;;IAGC;AACD;;;;;EAGC;AACD,qGAAqG;AACrG;;QAwCC;AAqBD;;;QAEC;AAgBD;;;;;;;;;QASC;AACD;;;;QAGC;AACD;;;;;;;;QA8BC;AAED,mHAAmH;AACnH,kDAgBC;AAED,+GAA+G;AAC/G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2EC"}
|
package/dist/forms/flows.d.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The form shell: declare inputs, run the loop, hand the result on. `after` = the form's own steps once complete.
|
|
3
|
-
* `confirm` ({ question, question_es } — SAY-style templates that may read cargo.forms[<id>].values; build them in
|
|
4
|
-
* `before_confirm` SET steps when they need computing): the exact confirmation, asked every time after the loop and
|
|
5
|
-
* never an input. YES runs `after`; NO runs `declined`; a correction given there goes through the form's own matching
|
|
6
|
-
* and the form runs again (so whatever the correction made missing is asked).
|
|
7
|
-
*/
|
|
8
1
|
export function formFlow({ id, name, version, description, inputs, after, extraVars, parameters, confirm, before_confirm, declined, stop, close_on_stop, resume_on_parent_change, before, done, primary, scope, validator, confirmation_condition, prompt, prompt_es, incomplete_outcome, on_exhausted }: {
|
|
9
2
|
id: any;
|
|
10
3
|
name: any;
|
|
@@ -265,6 +258,7 @@ export namespace FIELD_PRESETS {
|
|
|
265
258
|
let label_es_9: string;
|
|
266
259
|
export { label_es_9 as label_es };
|
|
267
260
|
export let accumulate: boolean;
|
|
261
|
+
export let accumulate_first: string;
|
|
268
262
|
export namespace profile_9 {
|
|
269
263
|
let kind_9: string;
|
|
270
264
|
export { kind_9 as kind };
|
|
@@ -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":"AAqHA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2DC;AACD;;;GAGG;AACH;;;;;;EAGC;AAyYD,wHAAwH;AACxH,wEAaC;AACD,4FAA4F;AAC5F;;;EAYC;AACD,sGAAsG;AACtG;;;EAOC;AA5jBM,6CAAuG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmvB9G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAOG"}
|
package/dist/forms/flows.js
CHANGED
|
@@ -65,7 +65,7 @@ export const FIELD_PRESETS = {
|
|
|
65
65
|
city: { label: 'city', label_es: 'ciudad', profile: { kind: 'text' }, question: 'What city?', question_es: '¿En qué ciudad?' },
|
|
66
66
|
state: { label: 'state', label_es: 'estado', profile: { kind: 'pattern', regex: '^\\s*(?:Alabama|Alaska|Arizona|Arkansas|California|Colorado|Connecticut|Delaware|Florida|Georgia|Hawaii|Idaho|Illinois|Indiana|Iowa|Kansas|Kentucky|Louisiana|Maine|Maryland|Massachusetts|Michigan|Minnesota|Mississippi|Missouri|Montana|Nebraska|Nevada|New Hampshire|New Jersey|New Mexico|New York|North Carolina|North Dakota|Ohio|Oklahoma|Oregon|Pennsylvania|Rhode Island|South Carolina|South Dakota|Tennessee|Texas|Utah|Vermont|Virginia|Washington|West Virginia|Wisconsin|Wyoming|District of Columbia|Puerto Rico|AL|AK|AZ|AR|CA|CO|CT|DE|FL|GA|HI|ID|IL|IN|IA|KS|KY|LA|ME|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|OH|OK|OR|PA|RI|SC|SD|TN|TX|UT|VT|VA|WA|WV|WI|WY|DC|PR)\\s*$' },
|
|
67
67
|
question: 'What state?', question_es: '¿En qué estado?' },
|
|
68
|
-
card_number: { label: 'card number', label_es: 'número de tarjeta', accumulate: true, profile: { kind: 'digits', min: 13, max: 19 }, digits: { min: 13, max: 19, complete: [{ first: '4', lengths: [16, 19] }, { first: '25', lengths: [16] }, { first: '3', lengths: [15] }, { first: '6', lengths: [16, 19] }] },
|
|
68
|
+
card_number: { label: 'card number', label_es: 'número de tarjeta', accumulate: true, accumulate_first: '^[2-6]', profile: { kind: 'digits', min: 13, max: 19 }, digits: { min: 13, max: 19, complete: [{ first: '4', lengths: [16, 19] }, { first: '25', lengths: [16] }, { first: '3', lengths: [15] }, { first: '6', lengths: [16, 19] }] },
|
|
69
69
|
pii: true, validator: 'validate-card-number',
|
|
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.' },
|
|
@@ -91,8 +91,29 @@ const FIELD_COLLECTORS = { 'collect-phone': 'phone', 'collect-email': 'email', '
|
|
|
91
91
|
* never an input. YES runs `after`; NO runs `declined`; a correction given there goes through the form's own matching
|
|
92
92
|
* and the form runs again (so whatever the correction made missing is asked).
|
|
93
93
|
*/
|
|
94
|
+
/**
|
|
95
|
+
* Context-aware acceptance (docs/copilot-reference.md §5) is standard for every form; its declarations are checked here,
|
|
96
|
+
* at definition time: a confirmation is a firm or soft choice keyed yes and no; invites list input names (of this form
|
|
97
|
+
* or an enclosing one, resolved at run time by scope).
|
|
98
|
+
*/
|
|
99
|
+
function validateAcceptanceDeclarations(id, inputs) {
|
|
100
|
+
for (const input of (inputs || []).flatMap((i) => i.any_of || [i])) {
|
|
101
|
+
if (input.confirmation !== undefined) {
|
|
102
|
+
const keys = (input.profile?.choices || []).map((choice) => choice.key);
|
|
103
|
+
if (!['firm', 'soft'].includes(input.confirmation) || input.profile?.kind !== 'choice' || !keys.includes('yes') || !keys.includes('no'))
|
|
104
|
+
throw new Error(`FORM ${id}: ${input.name} confirmation must be "firm" or "soft" on a choice keyed yes and no`);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// not a confirmation
|
|
108
|
+
}
|
|
109
|
+
if (input.invites !== undefined && (!Array.isArray(input.invites) || !input.invites.every((name) => typeof name === 'string' && name)))
|
|
110
|
+
throw new Error(`FORM ${id}: ${input.name} invites must list input names`);
|
|
111
|
+
else { /* no invites, or a list of names */ }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
94
114
|
export function formFlow({ id, name, version, description, inputs, after = [], extraVars = {}, parameters = [], confirm = null, before_confirm = [], declined = [], stop = '', close_on_stop = false, resume_on_parent_change = false, before = [], done = true, primary = false, scope = 'isolated', validator = '', confirmation_condition = 'true', prompt = undefined, prompt_es = undefined, incomplete_outcome = null, on_exhausted = null }) {
|
|
95
115
|
validateFormExhaustion(on_exhausted);
|
|
116
|
+
validateAcceptanceDeclarations(id, inputs);
|
|
96
117
|
const incompleteFrom = incomplete_outcome && Object.hasOwn(incomplete_outcome, 'from');
|
|
97
118
|
if (incompleteFrom) {
|
|
98
119
|
if (Object.keys(incomplete_outcome).length !== 1 || typeof incomplete_outcome.from !== 'string' || !/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(incomplete_outcome.from) || !parameters.some(p => p.name === incomplete_outcome.from.split('.')[0] && p.type === 'object'))
|
|
@@ -105,7 +126,7 @@ export function formFlow({ id, name, version, description, inputs, after = [], e
|
|
|
105
126
|
// FLOW parameters are paths only: the question is built here, as a SAY-style template, then passed by name
|
|
106
127
|
SET(`${id}_confirm_q`, 'form_confirm_q', textValue(confirm.question)),
|
|
107
128
|
SET(`${id}_confirm_q_es`, 'form_confirm_q_es', textValue(confirm.question_es || confirm.question)),
|
|
108
|
-
FLOW(`${id}_confirm`, 'confirm-form', 'call', { form: '{{form_instance}}', question: '{{form_confirm_q}}', question_es: '{{form_confirm_q_es}}', phrases: '{{form_confirmation.phrases}}', words: '{{form_confirmation.words}}', clarifications: '{{form_confirmation.clarifications}}', no_keys: '{{form_confirmation.no_keys}}', interrupts: '{{form_confirmation.interrupts}}', named_fields: '{{form_confirmation.named_fields}}', exit_context: '{{exit_context}}', exit_context_es: '{{exit_context_es}}' }),
|
|
129
|
+
FLOW(`${id}_confirm`, 'confirm-form', 'call', { form: '{{form_instance}}', question: '{{form_confirm_q}}', question_es: '{{form_confirm_q_es}}', phrases: '{{form_confirmation.phrases}}', words: '{{form_confirmation.words}}', clarifications: '{{form_confirmation.clarifications}}', no_keys: '{{form_confirmation.no_keys}}', interrupts: '{{form_confirmation.interrupts}}', invites: '{{form_confirmation.invites}}', named_fields: '{{form_confirmation.named_fields}}', exit_context: '{{exit_context}}', exit_context_es: '{{exit_context_es}}' }),
|
|
109
130
|
{ id: `${id}_confirmed`, type: 'CASE', branches: {
|
|
110
131
|
[`condition: (cargo.formConfirm || {})[form_instance] === 'changed'`]: FLOW(`${id}_again`, id, 'replace', { parent_form: '{{parent_form}}', result_input: '{{result_input}}', form_instance: '{{form_instance}}' }),
|
|
111
132
|
// NO: the declined flow takes the form's place, so the action below never runs
|
|
@@ -253,10 +274,11 @@ const collectInput = {
|
|
|
253
274
|
WHEN('ci_routed_end', 'ci_routed', END('ci_routed_done')),
|
|
254
275
|
// `accumulate` (opt-in, digit inputs): a number said in groups across turns ("4347", "6971 2210"…) is kept and joined;
|
|
255
276
|
// short of the input's minimum the next question is "Go on." Anything that is not digits drops the partial.
|
|
256
|
-
SET('ci_partial_join', 'ci_partial', "(function () { var state = cargo.forms[form]; if (!ci_spec || ci_spec.accumulate !== true || !ci_spec.digits) return ''; var said = String(cargo.choiceText || '').trim(); var data = state.data = state.data || {}; var key = '__partial_' + current; if (!/^[0-9][0-9\\s.,-]*$/.test(said)) { delete data[key]; return ''; } var joined = String(data[key] || '') + said.replace(/[^0-9]/g, ''); if (joined.length < Number(ci_spec.digits.min || 0)) { data[key] = joined; var fb = state.feedback || {}; fb[current] = 'partial'; state.feedback = fb; return 'hold'; } delete data[key]; cargo.choiceText = joined; cargo.choice = joined; return 'joined'; })()"),
|
|
277
|
+
SET('ci_partial_join', 'ci_partial', "(function () { var state = cargo.forms[form]; if (!ci_spec || ci_spec.accumulate !== true || !ci_spec.digits) return ''; var said = String(cargo.choiceText || '').trim(); var data = state.data = state.data || {}; var key = '__partial_' + current; if (!/^[0-9][0-9\\s.,-]*$/.test(said)) { delete data[key]; return ''; } var joined = String(data[key] || '') + said.replace(/[^0-9]/g, ''); var starts = ci_spec.accumulate_first ? new RegExp(ci_spec.accumulate_first) : null; if (!data[key] && starts && !starts.test(joined)) return ''; if (joined.length < Number(ci_spec.digits.min || 0)) { data[key] = joined; var fb = state.feedback || {}; fb[current] = 'partial'; state.feedback = fb; return 'hold'; } delete data[key]; cargo.choiceText = joined; cargo.choice = joined; return 'joined'; })()"),
|
|
257
278
|
WHEN('ci_partial_hold', "ci_partial === 'hold'", END('ci_partial_wait')),
|
|
258
279
|
SET('ci_build_chain', 'ci_chain', CHAIN_JS),
|
|
259
280
|
SET('ci_match', 'ci_m', "readFormAnswer(cargo.choiceText, ci_chain, Array.isArray(ci_spec.any_of) ? { form: form, names: ci_names } : { form: form, name: current }, cargo.choice, provides && Array.isArray(questions) ? questions : [])"),
|
|
281
|
+
SIDE('ci_acceptance_trace', "(cargo.formAcceptance = formAcceptanceTrace(cargo.formAcceptance, ci_m), true)"),
|
|
260
282
|
// The labels and validators ride on each reading, so the presets stay in this one flow.
|
|
261
283
|
SET('ci_enrich', 'ci_m', 'enrichFormMatches(ci_m, ci_chain, form, ci_names, current)'),
|
|
262
284
|
SET('ci_question_key', 'ci_qkey', "ci_m.questions.length ? ci_m.questions[0] : !ci_m.hits.length && !ci_m.ambiguous.length && Array.isArray(questions) && questions.length && provides ? pickChoice(cargo.choiceText, questions.map(function(question){return Object.assign({},question,{exact:true});}), false) : ''"),
|
|
@@ -425,8 +447,8 @@ const disambiguate = {
|
|
|
425
447
|
const confirmForm = {
|
|
426
448
|
id: 'confirm-form', name: 'ConfirmForm', version: '1.0.0', scope: 'isolated',
|
|
427
449
|
description: "Worker: ask a form's confirmation (exact YES / NO; keys 1 and 2). A correction instead ('no, make it $40') is read against the form chain and stored, and the result is 'changed' so the form runs again; an unclear short answer is asked again, a long unrelated one is DISPATCHed. Result: cargo.formConfirm[form] = 'yes' | 'no' | 'changed'. Params: form, question(_es), exit_context(_es).",
|
|
428
|
-
parameters: ['form', 'question', 'question_es', 'exit_context', 'exit_context_es', 'phrases', 'words', 'interrupts', 'no_keys', 'clarifications'].map((n) => ({ name: n, type: ['phrases', 'words', 'interrupts', 'no_keys', 'clarifications'].includes(n) ? 'array' : 'string', description: n })).concat([{ name: 'named_fields', type: 'boolean', description: "true: an answer that names one of the form's inputs (its names) without a value re-asks only that input" }]),
|
|
429
|
-
variables: vars({ named_fields: false, cf_named: null, cf_presets: FIELD_PRESETS, cf_chain: [], cf_m: null, cf_current: null, cf_result: '', cf_prompt: '', cf_prompt_es: '', cf_words: 0, cf_tries: 0, phrases: null, words: [], clarifications: [], no_keys: ['2'], interrupts: [], cf_choices: [], cf_event: '', cf_amount: false, cf_questions: [], cf_provides: null, cf_skip_gate: '' }),
|
|
450
|
+
parameters: ['form', 'question', 'question_es', 'exit_context', 'exit_context_es', 'phrases', 'words', 'interrupts', 'no_keys', 'clarifications', 'invites'].map((n) => ({ name: n, type: ['phrases', 'words', 'interrupts', 'no_keys', 'clarifications', 'invites'].includes(n) ? 'array' : 'string', description: n })).concat([{ name: 'named_fields', type: 'boolean', description: "true: an answer that names one of the form's inputs (its names) without a value re-asks only that input" }]),
|
|
451
|
+
variables: vars({ named_fields: false, cf_named: null, cf_presets: FIELD_PRESETS, cf_chain: [], cf_m: null, cf_current: null, cf_result: '', cf_prompt: '', cf_prompt_es: '', cf_words: 0, cf_tries: 0, phrases: null, words: [], clarifications: [], no_keys: ['2'], interrupts: [], invites: [], cf_choices: [], cf_event: '', cf_amount: false, cf_questions: [], cf_provides: null, cf_skip_gate: '' }),
|
|
430
452
|
steps: [
|
|
431
453
|
SIDE('cf_reset', "(cargo.formConfirm = cargo.formConfirm || {}, cargo.formConfirm[form] = '', true)"),
|
|
432
454
|
SET('cf_choices', 'cf_choices', `[{key:'yes',phrases:['1'].concat(Array.isArray(phrases)?phrases:Array.isArray(words)?words:[]),exact:true},{key:'no',phrases:(Array.isArray(no_keys)?no_keys:['2']).concat(${JSON.stringify(ANSWER_WORDS.no)}),exact:true}].concat((Array.isArray(interrupts)?interrupts:[]).map(function(event){return {key:'event:'+event.key,phrases:event.phrases ?? event.words,exact:event.exact!==false};}))`),
|
|
@@ -443,8 +465,9 @@ const confirmForm = {
|
|
|
443
465
|
SET('cf_question_catalog', 'cf_questions', 'cargo.forms[form].questions || []'),
|
|
444
466
|
SET('cf_provider_catalog', 'cf_provides', 'cargo.forms[form].providers'),
|
|
445
467
|
SET('cf_gate_policy', 'cf_skip_gate', "!cf_provides || !cf_provides.gate || [].concat(cf_provides.gate.verified || []).every(function(p){return !!formPath({cargo:cargo},p);}) ? 'true' : ''"),
|
|
446
|
-
SET('cf_current', 'cf_current', "cargo.forms[form].proposalPrompt ? {form:cargo.forms[form].parent,name:cargo.forms[form].proposalPrompt.input} : {form:form,name:''}"),
|
|
468
|
+
SET('cf_current', 'cf_current', "cargo.forms[form].proposalPrompt ? {form:cargo.forms[form].parent,name:cargo.forms[form].proposalPrompt.input} : {form:form,name:'',invites:Array.isArray(invites)?invites:[]}"),
|
|
447
469
|
SET('cf_match', 'cf_m', "cargo.choice === 'yes' || cargo.choice === 'no' && pickChoice(cargo.choiceText, [{key:'no',phrases:Array.isArray(no_keys)?no_keys:['2'],exact:true}], false) === 'no' ? { hits: [], ambiguous: [], questions:[] } : readFormAnswer(cargo.choiceText, cf_chain, cf_current, '',cf_questions)"),
|
|
470
|
+
SIDE('cf_acceptance_trace', "(cargo.formAcceptance = formAcceptanceTrace(cargo.formAcceptance, cf_m), true)"),
|
|
448
471
|
SET('cf_enrich', 'cf_m', "(function(m){m.hits=m.hits.map(function(h){return Object.assign({},h,{silent:true});});return m;})(enrichFormMatches(cf_m, cf_chain, form))"),
|
|
449
472
|
SET('cf_count_words', 'cf_words', "String(cargo.choiceText || '').split(/\\s+/).filter(function (w) { return /[a-záéíóúüñ]/i.test(w); }).length"),
|
|
450
473
|
// opt-in (confirm.named_fields): 'the city is wrong' / 'la ciudad no' names exactly one input and gives no value; that
|
|
@@ -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;AAgFD,4DAKC;
|
|
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;AAgFD,4DAKC;AAiFD,8EAGC;AA4BD,4FAA4F;AAC5F;;;;EAOC;AAQD,0BAAmI"}
|
|
@@ -184,7 +184,9 @@ const code = worker('get-and-validate-otp-code', 'GetAndValidateOtpCode', 'Colle
|
|
|
184
184
|
] });
|
|
185
185
|
function otpMenu(id, error, error_es, allow_code_entry = false) { const en = id + '_message', es = id + '_message_es'; otpMenuMessages.push([en, error], [es, error_es]); return call(id, 'generic-retry-with-options', { error_message: '{{' + en + '}}', error_message_es: '{{' + es + '}}', retry_flow: 'authenticate-user', retry_call_type: 'replace', cancel_flow: '{{cargo.authCancelFlow}}', allow_code_entry, capture_patterns: '{{cargo.authCapture}}', parent_form: '{{cargo.authOptions.parent_form}}', question: 'Would you like to try again to get access to your account?', question_es: '¿Le gustaría intentar nuevamente obtener acceso a su cuenta?' }, 'replace'); }
|
|
186
186
|
const OTP_WORDS = {
|
|
187
|
-
other: ['otra cuenta', 'otro número', 'otro numero', 'cuenta diferente', 'another account', 'different account', 'other account', 'another number', 'different number', 'not my account', 'no es mi cuenta'],
|
|
187
|
+
other: ['otra cuenta', 'otro número', 'otro numero', 'cuenta diferente', 'another account', 'different account', 'other account', 'another number', 'different number', 'not my account', 'no es mi cuenta', 'número de cuenta', 'numero de cuenta', 'account number'],
|
|
188
|
+
// the offered OTRA CUENTA / ANOTHER ACCOUNT said by its first word, as the whole answer (prod 2026-10-25 00:19: "Otra")
|
|
189
|
+
other_alone: ['otra', 'otro', 'another', 'other'],
|
|
188
190
|
resend: ['resend', 'send again', 'send it again', 'not received', 'never received', 'did not receive', 'didn t receive', 'didn t get', 'haven t received', 'nothing came', 'no code', 'no text', 'no llegó', 'no llego', 'no llega', 'no ha llegado', 'no me llegó', 'no me llego', 'no le llegó', 'no le llego', 'no me ha llegado', 'no recibí', 'no recibi', 'no lo recibí', 'no lo recibi', 'no tengo el código', 'no tengo el codigo', 'no lo tengo', 'no alcancé', 'no alcance', 'mándame el código', 'mandame el codigo', 'envíame el código', 'enviame el codigo', 'reenviar', 'reenvíe', 'reenvie'],
|
|
189
191
|
later: ['driving', 'i am driving', 'i m busy', 'im busy', 'not now', 'later', 'call me later', 'call back', 'cant right now', 'can t right now', 'can t now', 'cannot right now', 'manejando', 'conduciendo', 'estoy manejando', 'voy manejando', 'ahora no', 'ahora no puedo', 'no puedo ahora', 'más tarde', 'mas tarde', 'estoy ocupado', 'estoy ocupada'],
|
|
190
192
|
noPhone: ['don t have that phone', 'don t have that number', 'don t have that phone anymore', 'don t have the phone', 'not my number anymore', 'not my phone anymore', 'that s an old number', 'old number', 'changed my number', 'changed my phone', 'lost my phone', 'no tengo ese teléfono', 'no tengo ese telefono', 'no tengo ese número', 'no tengo ese numero', 'ya no tengo ese', 'ya no tengo el teléfono', 'ya no tengo el telefono', 'ya no es mi número', 'ya no es mi numero', 'número viejo', 'numero viejo', 'cambié de número', 'cambie de numero', 'cambié mi número', 'perdí mi teléfono', 'perdi mi telefono', 'don t have a phone', 'don t have phone', 'don t have my phone', 'don t have a cell', 'don t have the cell', 'don t have my cell', 'don t have a cell phone', 'don t have access to that phone', 'don t have access to the phone', 'no tengo teléfono', 'no tengo telefono', 'no tengo celular', 'no tengo el celular', 'no tengo mi teléfono', 'no tengo mi telefono', 'no tengo mi celular', 'no tengo un teléfono', 'no tengo un telefono', 'no tengo acceso a ese teléfono', 'no tengo acceso a ese telefono']
|
|
@@ -201,7 +203,7 @@ const validateCode = worker('validate-otp-code', 'ValidateOtpCode', 'Validate a
|
|
|
201
203
|
set('otp_reset_no_code', 'no_code_given', 'false'), set('otp_reset_invite', 'whatsapp_invite_sent', 'false'), set('otp_reset_callback', 'answer_key', "''"),
|
|
202
204
|
when('otp_tenant_code', 'cargo.authOptions && cargo.authOptions.code_validator && normalized_otp_code.length !== 6 && /[0-9]/.test(user_choice)', call('otp_tenant_code_call', '{{cargo.authOptions.code_validator}}', { answer: '{{user_choice}}' })),
|
|
203
205
|
set('otp_other_given', 'other_account_given', "answer_key === 'another_account' ? (cargo.authOtherAccount=String(answer_value || ''),true) : false"),
|
|
204
|
-
set('otp_other_requested', 'wants_other_account', 'other_account_given || !!(cargo.callerPreselected && normalized_otp_code.length !== 6 && matchesChoice(user_choice,otp_phrases.other))'),
|
|
206
|
+
set('otp_other_requested', 'wants_other_account', 'other_account_given || !!(cargo.callerPreselected && normalized_otp_code.length !== 6 && (matchesChoice(user_choice,otp_phrases.other) || otp_phrases.other_alone.includes(user_choice.replace(/[.,!?¡¿]/g,\'\').trim())))'),
|
|
205
207
|
when('otp_tenant_answer', 'cargo.authOptions && cargo.authOptions.answer_handler && normalized_otp_code.length !== 6 && !wants_other_account', call('otp_tenant_answer_call', '{{cargo.authOptions.answer_handler}}', { answer: '{{user_choice}}' })),
|
|
206
208
|
cases('otp_response', {
|
|
207
209
|
'condition: normalized_otp_code.length === 6': { id: 'otp_check_tool', type: 'CALL-TOOL', tool: 'validate-otp', variable: 'otp_validation_result', args: { otp: '{{normalized_otp_code}}', container: '{{cargo}}' }, onFail: call('otp_check_failed', 'get-and-validate-otp-code', undefined, 'replace') },
|
package/dist/helpers/index.d.ts
CHANGED
|
@@ -71,6 +71,7 @@ export namespace STANDARD_FUNCTIONS {
|
|
|
71
71
|
export { matchInputs };
|
|
72
72
|
export { readFormAnswer };
|
|
73
73
|
export { readInputProfile };
|
|
74
|
+
export { formAcceptanceTrace };
|
|
74
75
|
export { resolveFormInputs };
|
|
75
76
|
export { formInputApplies };
|
|
76
77
|
export { formInputFilled };
|
|
@@ -187,6 +188,7 @@ import { pickProviderQuestion } from './form-sources.js';
|
|
|
187
188
|
import { matchInputs } from './match-inputs.js';
|
|
188
189
|
import { readFormAnswer } from './match-inputs.js';
|
|
189
190
|
import { readInputProfile } from './match-inputs.js';
|
|
191
|
+
import { formAcceptanceTrace } from './match-inputs.js';
|
|
190
192
|
import { resolveFormInputs } from './form-state.js';
|
|
191
193
|
import { formInputApplies } from './form-state.js';
|
|
192
194
|
import { formInputFilled } from './form-state.js';
|
|
@@ -232,5 +234,5 @@ import { validatePhone } from './validators.js';
|
|
|
232
234
|
import { validateEmail } from './validators.js';
|
|
233
235
|
import { urlToSpeech } from './url-speech.js';
|
|
234
236
|
import { textWithUrlToSpeech } from './url-speech.js';
|
|
235
|
-
export { continuationSafetyProtocol, cancelFormTask, contextualResponseProtocol, validateFormResponse, hasFormResponse, formProposalPolicy, stageFormProposal, nextFormProposal, formProposalReadings, bindFormProposal, decideFormProposal, commitFormProposals, settleFormProposalValidation, formProposalProtocol, validateFormProposalPolicy, validateFormProposalDescriptor, formProposalDescriptor, clearFormMisses, missFormInput, formInputPrompt, consumeFormFeedback, formConversationOutcome, readMoneyAmount, isMoneyAmount, moneyIntentAllows, isPastPaymentStatement, isPartialMoneyAnswer, validateMoneyOptions, takeChoiceOffer, matchesChoice, ANSWER_WORDS, CONFIRM_WORDS, isAnswer, isAgentRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice, readEmail, readPhone, readInternationalPhone, readIdentifier, last4, maskEmail, callerTextNumber, textDestination, callerIsInternational, normalizeAndFindCapture, anythingElse, goodbye, sayName, dateToWords, amountToSpeech, validateCardNumber, validateExpiration, consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFormAction, confirmFormRevision, prepareFormAction, actionConfirmationValid, beginFormAction, finishFormAction, actionGateVerified, sourceNeedsLoad, beginSourceRefresh, completeSourceRefresh, pickProviderQuestion, matchInputs, readFormAnswer, readInputProfile, resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, formInputChain, openFormInstance, validateFormIncompleteOutcome, formIncompleteOutcome, validateFormExhaustion, exhaustFormInput, formInitialReadings, orderFormReadings, proposedFormValues, applyFormValidation, formReadingDependencies, exclusiveFormReadings, enrichFormMatches, expandFormActivations, settleFormActivations, rememberFormArguments, resetFormInputs, hasFormValue, formPath, formValuesEqual, formReadingReady, formReadingRelevant, formPreparation, queueFormReadings, pendingFormReadings, formWorkLimit, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, formParentResume, closeFormInstance, checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech };
|
|
237
|
+
export { continuationSafetyProtocol, cancelFormTask, contextualResponseProtocol, validateFormResponse, hasFormResponse, formProposalPolicy, stageFormProposal, nextFormProposal, formProposalReadings, bindFormProposal, decideFormProposal, commitFormProposals, settleFormProposalValidation, formProposalProtocol, validateFormProposalPolicy, validateFormProposalDescriptor, formProposalDescriptor, clearFormMisses, missFormInput, formInputPrompt, consumeFormFeedback, formConversationOutcome, readMoneyAmount, isMoneyAmount, moneyIntentAllows, isPastPaymentStatement, isPartialMoneyAnswer, validateMoneyOptions, takeChoiceOffer, matchesChoice, ANSWER_WORDS, CONFIRM_WORDS, isAnswer, isAgentRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice, readEmail, readPhone, readInternationalPhone, readIdentifier, last4, maskEmail, callerTextNumber, textDestination, callerIsInternational, normalizeAndFindCapture, anythingElse, goodbye, sayName, dateToWords, amountToSpeech, validateCardNumber, validateExpiration, consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFormAction, confirmFormRevision, prepareFormAction, actionConfirmationValid, beginFormAction, finishFormAction, actionGateVerified, sourceNeedsLoad, beginSourceRefresh, completeSourceRefresh, pickProviderQuestion, matchInputs, readFormAnswer, readInputProfile, formAcceptanceTrace, resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, formInputChain, openFormInstance, validateFormIncompleteOutcome, formIncompleteOutcome, validateFormExhaustion, exhaustFormInput, formInitialReadings, orderFormReadings, proposedFormValues, applyFormValidation, formReadingDependencies, exclusiveFormReadings, enrichFormMatches, expandFormActivations, settleFormActivations, rememberFormArguments, resetFormInputs, hasFormValue, formPath, formValuesEqual, formReadingReady, formReadingRelevant, formPreparation, queueFormReadings, pendingFormReadings, formWorkLimit, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, formParentResume, closeFormInstance, checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech };
|
|
236
238
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/helpers/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/helpers/index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2CACyC,iCAAiC;+BAI6C,wBAAwB;2CAHjE,0BAA0B;qCAA1B,0BAA0B;gCAA1B,0BAA0B;mCACsE,qBAAqB;kCAArB,qBAAqB;iCAArB,qBAAqB;qCAArB,qBAAqB;iCAArB,qBAAqB;mCAArB,qBAAqB;oCAArB,qBAAqB;6CAArB,qBAAqB;qCAC/E,wBAAwB;2CAAxB,wBAAwB;+CAAxB,wBAAwB;uCAAxB,wBAAwB;gCACrB,wBAAwB;8BAAxB,wBAAwB;gCAAxB,wBAAwB;oCAAxB,wBAAwB;wCAAxB,wBAAwB;gCAmBhB,YAAY;8BAAZ,YAAY;kCAAZ,YAAY;uCAAZ,YAAY;qCAAZ,YAAY;qCAAZ,YAAY;gCAlB3G,mBAAmB;8BAiBrB,qBAAqB;6BAEkG,aAAa;8BAAb,aAAa;yBAAb,aAAa;+BAAb,aAAa;+BAAb,aAAa;kCAAb,aAAa;+BAAb,aAAa;+BAAb,aAAa;2BAAb,aAAa;0BACT,cAAc;0BAAd,cAAc;uCAAd,cAAc;+BAAd,cAAc;sBAAd,cAAc;0BAAd,cAAc;iCAAd,cAAc;gCAAd,cAAc;sCAAd,cAAc;wCAC/H,cAAc;6BACP,cAAc;wBAAd,cAAc;wBAAd,cAAc;4BACjC,iBAAiB;+BACd,uBAAuB;mCACC,kBAAkB;mCAAlB,kBAAkB;qCAxB+I,mBAAmB;yCAAnB,mBAAmB;8BAAnB,mBAAmB;iCAAnB,mBAAmB;oCAAnB,mBAAmB;kCAAnB,mBAAmB;wCAAnB,mBAAmB;gCAAnB,mBAAmB;iCAAnB,mBAAmB;mCAAnB,mBAAmB;gCAe1I,mBAAmB;mCAAnB,mBAAmB;sCAAnB,mBAAmB;qCAAnB,mBAAmB;4BAUjC,mBAAmB;+BAAnB,mBAAmB;iCAAnB,mBAAmB;oCAAnB,mBAAmB;kCAC0mB,iBAAiB;iCAAjB,iBAAiB;gCAAjB,iBAAiB;8BAAjB,iBAAiB;+BAAjB,iBAAiB;iCAAjB,iBAAiB;8CAAjB,iBAAiB;sCAAjB,iBAAiB;uCAAjB,iBAAiB;iCAAjB,iBAAiB;oCAAjB,iBAAiB;kCAAjB,iBAAiB;mCAAjB,iBAAiB;oCAAjB,iBAAiB;wCAAjB,iBAAiB;sCAAjB,iBAAiB;kCAAjB,iBAAiB;sCAAjB,iBAAiB;sCAAjB,iBAAiB;sCAAjB,iBAAiB;gCAAjB,iBAAiB;6BAAjB,iBAAiB;yBAAjB,iBAAiB;gCAAjB,iBAAiB;iCAAjB,iBAAiB;oCAAjB,iBAAiB;gCAAjB,iBAAiB;kCAAjB,iBAAiB;oCAAjB,iBAAiB;8BAAjB,iBAAiB;mCAAjB,iBAAiB;qCAAjB,iBAAiB;mCAAjB,iBAAiB;oCAAjB,iBAAiB;iCAAjB,iBAAiB;kCAAjB,iBAAiB;yBAC3pB,gBAAgB;gCAAhB,gBAAgB;4BAAhB,gBAAgB;8BAAhB,gBAAgB;+BACzB,iBAAiB;8BAAjB,iBAAiB;8BAAjB,iBAAiB;4BAC7B,iBAAiB;oCAAjB,iBAAiB"}
|
package/dist/helpers/index.js
CHANGED
|
@@ -30,12 +30,12 @@ import { anythingElse, goodbye, sayName } from './phrases.js';
|
|
|
30
30
|
import { dateToWords } from './date-words.js';
|
|
31
31
|
import { amountToSpeech } from './amount-to-speech.js';
|
|
32
32
|
import { validateCardNumber, validateExpiration } from './card-checks.js';
|
|
33
|
-
import { matchInputs, readFormAnswer, readInputProfile } from './match-inputs.js';
|
|
33
|
+
import { matchInputs, readFormAnswer, readInputProfile, formAcceptanceTrace } from './match-inputs.js';
|
|
34
34
|
import { resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, formInputChain, openFormInstance, validateFormIncompleteOutcome, formIncompleteOutcome, validateFormExhaustion, exhaustFormInput, formInitialReadings, orderFormReadings, proposedFormValues, applyFormValidation, formReadingDependencies, exclusiveFormReadings, enrichFormMatches, expandFormActivations, settleFormActivations, rememberFormArguments, resetFormInputs, hasFormValue, formPath, formValuesEqual, formReadingReady, formReadingRelevant, formPreparation, queueFormReadings, pendingFormReadings, formWorkLimit, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, formParentResume, closeFormInstance } from './form-state.js';
|
|
35
35
|
import { checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS } from './otp-check.js';
|
|
36
36
|
import { validateDigits, validatePhone, validateEmail } from './validators.js';
|
|
37
37
|
import { urlToSpeech, textWithUrlToSpeech } from './url-speech.js';
|
|
38
|
-
export { continuationSafetyProtocol, cancelFormTask, contextualResponseProtocol, validateFormResponse, hasFormResponse, formProposalPolicy, stageFormProposal, nextFormProposal, formProposalReadings, bindFormProposal, decideFormProposal, commitFormProposals, settleFormProposalValidation, formProposalProtocol, validateFormProposalPolicy, validateFormProposalDescriptor, formProposalDescriptor, clearFormMisses, missFormInput, formInputPrompt, consumeFormFeedback, formConversationOutcome, readMoneyAmount, isMoneyAmount, moneyIntentAllows, isPastPaymentStatement, isPartialMoneyAnswer, validateMoneyOptions, takeChoiceOffer, matchesChoice, ANSWER_WORDS, CONFIRM_WORDS, isAnswer, isAgentRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice, readEmail, readPhone, readInternationalPhone, readIdentifier, last4, maskEmail, callerTextNumber, textDestination, callerIsInternational, normalizeAndFindCapture, anythingElse, goodbye, sayName, dateToWords, amountToSpeech, validateCardNumber, validateExpiration, consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFormAction, confirmFormRevision, prepareFormAction, actionConfirmationValid, beginFormAction, finishFormAction, actionGateVerified, sourceNeedsLoad, beginSourceRefresh, completeSourceRefresh, pickProviderQuestion, matchInputs, readFormAnswer, readInputProfile, resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, formInputChain, openFormInstance, validateFormIncompleteOutcome, formIncompleteOutcome, validateFormExhaustion, exhaustFormInput, formInitialReadings, orderFormReadings, proposedFormValues, applyFormValidation, formReadingDependencies, exclusiveFormReadings, enrichFormMatches, expandFormActivations, settleFormActivations, rememberFormArguments, resetFormInputs, hasFormValue, formPath, formValuesEqual, formReadingReady, formReadingRelevant, formPreparation, queueFormReadings, pendingFormReadings, formWorkLimit, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, formParentResume, closeFormInstance, checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech, };
|
|
38
|
+
export { continuationSafetyProtocol, cancelFormTask, contextualResponseProtocol, validateFormResponse, hasFormResponse, formProposalPolicy, stageFormProposal, nextFormProposal, formProposalReadings, bindFormProposal, decideFormProposal, commitFormProposals, settleFormProposalValidation, formProposalProtocol, validateFormProposalPolicy, validateFormProposalDescriptor, formProposalDescriptor, clearFormMisses, missFormInput, formInputPrompt, consumeFormFeedback, formConversationOutcome, readMoneyAmount, isMoneyAmount, moneyIntentAllows, isPastPaymentStatement, isPartialMoneyAnswer, validateMoneyOptions, takeChoiceOffer, matchesChoice, ANSWER_WORDS, CONFIRM_WORDS, isAnswer, isAgentRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice, readEmail, readPhone, readInternationalPhone, readIdentifier, last4, maskEmail, callerTextNumber, textDestination, callerIsInternational, normalizeAndFindCapture, anythingElse, goodbye, sayName, dateToWords, amountToSpeech, validateCardNumber, validateExpiration, consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFormAction, confirmFormRevision, prepareFormAction, actionConfirmationValid, beginFormAction, finishFormAction, actionGateVerified, sourceNeedsLoad, beginSourceRefresh, completeSourceRefresh, pickProviderQuestion, matchInputs, readFormAnswer, readInputProfile, formAcceptanceTrace, resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, formInputChain, openFormInstance, validateFormIncompleteOutcome, formIncompleteOutcome, validateFormExhaustion, exhaustFormInput, formInitialReadings, orderFormReadings, proposedFormValues, applyFormValidation, formReadingDependencies, exclusiveFormReadings, enrichFormMatches, expandFormActivations, settleFormActivations, rememberFormArguments, resetFormInputs, hasFormValue, formPath, formValuesEqual, formReadingReady, formReadingRelevant, formPreparation, queueFormReadings, pendingFormReadings, formWorkLimit, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, formParentResume, closeFormInstance, checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech, };
|
|
39
39
|
/** The functions flows call by name, ready to spread into a host's approved functions. */
|
|
40
40
|
export const STANDARD_FUNCTIONS = {
|
|
41
41
|
continuationSafetyProtocol, cancelFormTask, contextualResponseProtocol, validateFormResponse, hasFormResponse,
|
|
@@ -43,6 +43,6 @@ export const STANDARD_FUNCTIONS = {
|
|
|
43
43
|
clearFormMisses, missFormInput, formInputPrompt, consumeFormFeedback, formConversationOutcome,
|
|
44
44
|
readMoneyAmount, isMoneyAmount, moneyIntentAllows, isPastPaymentStatement, isPartialMoneyAnswer, validateMoneyOptions, takeChoiceOffer, matchesChoice, isAnswer, isAgentRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice,
|
|
45
45
|
readEmail, readPhone, readInternationalPhone, readIdentifier, last4, maskEmail, callerTextNumber, textDestination, callerIsInternational,
|
|
46
|
-
normalizeAndFindCapture, anythingElse, goodbye, sayName, dateToWords, amountToSpeech, validateCardNumber, validateExpiration, consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFormAction, confirmFormRevision, prepareFormAction, actionConfirmationValid, beginFormAction, finishFormAction, actionGateVerified, sourceNeedsLoad, beginSourceRefresh, completeSourceRefresh, pickProviderQuestion, matchInputs, readFormAnswer, readInputProfile, resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, formInputChain, openFormInstance, validateFormIncompleteOutcome, formIncompleteOutcome, validateFormExhaustion, exhaustFormInput, formInitialReadings, orderFormReadings, proposedFormValues, applyFormValidation, formReadingDependencies, exclusiveFormReadings, enrichFormMatches, expandFormActivations, settleFormActivations, rememberFormArguments, resetFormInputs, hasFormValue, formPath, formValuesEqual, formReadingReady, formReadingRelevant, formPreparation, queueFormReadings, pendingFormReadings, formWorkLimit, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, formParentResume, closeFormInstance,
|
|
46
|
+
normalizeAndFindCapture, anythingElse, goodbye, sayName, dateToWords, amountToSpeech, validateCardNumber, validateExpiration, consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFormAction, confirmFormRevision, prepareFormAction, actionConfirmationValid, beginFormAction, finishFormAction, actionGateVerified, sourceNeedsLoad, beginSourceRefresh, completeSourceRefresh, pickProviderQuestion, matchInputs, readFormAnswer, readInputProfile, formAcceptanceTrace, resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, formInputChain, openFormInstance, validateFormIncompleteOutcome, formIncompleteOutcome, validateFormExhaustion, exhaustFormInput, formInitialReadings, orderFormReadings, proposedFormValues, applyFormValidation, formReadingDependencies, exclusiveFormReadings, enrichFormMatches, expandFormActivations, settleFormActivations, rememberFormArguments, resetFormInputs, hasFormValue, formPath, formValuesEqual, formReadingReady, formReadingRelevant, formPreparation, queueFormReadings, pendingFormReadings, formWorkLimit, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, formParentResume, closeFormInstance,
|
|
47
47
|
validateOTP, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech,
|
|
48
48
|
};
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This turn's acceptance decisions for the host to log, appended per match: fields filled out of order, in a compound
|
|
3
|
+
* answer or as an invited input, and every refused reading, by form, field and rule. Field names and rules only, never
|
|
4
|
+
* values: the answers include card numbers and codes. The host clears it at the start of a turn.
|
|
5
|
+
*/
|
|
6
|
+
export function formAcceptanceTrace(previous: any, matches: any): any;
|
|
1
7
|
/**
|
|
2
8
|
* One input's reading of the answer: { value, evidence } or null. `evidence` is what the answer spent on it (digits
|
|
3
9
|
* or words), so two inputs that claim the same part of the answer are told apart from a compound answer.
|
|
@@ -31,8 +37,40 @@ export function readFormAnswer(answer: any, candidates: any, current: any, choic
|
|
|
31
37
|
ambiguous: any;
|
|
32
38
|
remaining: any[];
|
|
33
39
|
questions: any[];
|
|
40
|
+
decisions?: {
|
|
41
|
+
form: any;
|
|
42
|
+
name: any;
|
|
43
|
+
rule: any;
|
|
44
|
+
}[] | undefined;
|
|
45
|
+
refused?: ({
|
|
46
|
+
form: any;
|
|
47
|
+
name: any;
|
|
48
|
+
rule: string;
|
|
49
|
+
value?: undefined;
|
|
50
|
+
} | {
|
|
51
|
+
form: any;
|
|
52
|
+
name: any;
|
|
53
|
+
value: any;
|
|
54
|
+
rule: string;
|
|
55
|
+
})[] | undefined;
|
|
34
56
|
};
|
|
35
57
|
export function matchInputs(answer: any, candidates: any, current: any, choiceKey: any): {
|
|
58
|
+
decisions?: {
|
|
59
|
+
form: any;
|
|
60
|
+
name: any;
|
|
61
|
+
rule: any;
|
|
62
|
+
}[] | undefined;
|
|
63
|
+
refused?: ({
|
|
64
|
+
form: any;
|
|
65
|
+
name: any;
|
|
66
|
+
rule: string;
|
|
67
|
+
value?: undefined;
|
|
68
|
+
} | {
|
|
69
|
+
form: any;
|
|
70
|
+
name: any;
|
|
71
|
+
value: any;
|
|
72
|
+
rule: string;
|
|
73
|
+
})[] | undefined;
|
|
36
74
|
remaining?: any[] | undefined;
|
|
37
75
|
hits: {
|
|
38
76
|
[x: string]: any;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"match-inputs.d.ts","sourceRoot":"","sources":["../../src/helpers/match-inputs.js"],"names":[],"mappings":"
|
|
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;;;;;;;;;;;;;;;;;;;;;;EA0KC"}
|
|
@@ -9,11 +9,26 @@
|
|
|
9
9
|
* the flows. Part of jsfe/helpers; imports only siblings.
|
|
10
10
|
*/
|
|
11
11
|
import { matchesChoice } from './matches-choice.js';
|
|
12
|
-
import { pickChoice, amountInAnswer, isAmountAnswer } from './answer.js';
|
|
12
|
+
import { pickChoice, amountInAnswer, isAmountAnswer, ANSWER_WORDS, CONFIRM_WORDS } from './answer.js';
|
|
13
13
|
import { readEmail, readPhone, readInternationalPhone, readIdentifier } from './contact.js';
|
|
14
14
|
import { readSpokenMoney } from './spoken-money.js';
|
|
15
15
|
import { readMoneyAmount, isPartialMoneyAnswer } from './money.js';
|
|
16
16
|
import { pickProviderQuestion } from './form-sources.js';
|
|
17
|
+
/**
|
|
18
|
+
* This turn's acceptance decisions for the host to log, appended per match: fields filled out of order, in a compound
|
|
19
|
+
* answer or as an invited input, and every refused reading, by form, field and rule. Field names and rules only, never
|
|
20
|
+
* values: the answers include card numbers and codes. The host clears it at the start of a turn.
|
|
21
|
+
*/
|
|
22
|
+
export function formAcceptanceTrace(previous, matches) {
|
|
23
|
+
const filled = (matches?.decisions || []).map((d) => ({ form: String(d.form).replace(/:\d+$/, ''), name: d.name, rule: d.rule }));
|
|
24
|
+
const refused = (matches?.refused || []).map((r) => ({ form: String(r.form).replace(/:\d+$/, ''), name: r.name, rule: r.rule }));
|
|
25
|
+
if (!filled.length && !refused.length) {
|
|
26
|
+
return previous ?? null;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
return { filled: [...(previous?.filled || []), ...filled], refused: [...(previous?.refused || []), ...refused] };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
17
32
|
/** The answer as said: jsfe stores user input escaped (don\'t). */
|
|
18
33
|
function unescape(answer) {
|
|
19
34
|
return String(answer ?? '').replace(/\\(['"\\])/g, '$1');
|
|
@@ -91,6 +106,15 @@ function unclaimedSpans(raw, readings) {
|
|
|
91
106
|
*/
|
|
92
107
|
export function readInputProfile(profile, answer, isCurrent = true) {
|
|
93
108
|
const raw = unescape(answer);
|
|
109
|
+
// A field may declare a narrower signature for out-of-order reading: the payment card takes a bare
|
|
110
|
+
// last four when asked, but out of order only a card word or "ending in 4242".
|
|
111
|
+
if (!isCurrent && profile && profile.out_of_order && typeof profile.out_of_order === 'object') {
|
|
112
|
+
const { out_of_order, ...asked } = profile;
|
|
113
|
+
return readInputProfile({ ...asked, ...out_of_order }, answer, isCurrent);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
// asked, or no out-of-order signature
|
|
117
|
+
}
|
|
94
118
|
const kind = profile && profile.kind;
|
|
95
119
|
if (kind === 'choice') {
|
|
96
120
|
const choices = (Array.isArray(profile.choices) ? profile.choices : []).filter(c => isCurrent || c.current_only !== true).map(c => ({ ...c,
|
|
@@ -271,9 +295,23 @@ export function matchInputs(answer, candidates, current, choiceKey) {
|
|
|
271
295
|
forms.push(c.scope || c.form);
|
|
272
296
|
}
|
|
273
297
|
else { /* already ranked */ } });
|
|
298
|
+
const fieldOf = (reading) => list.find((c) => c.form === reading.form && c.name === reading.name) || { profile: {} };
|
|
299
|
+
const spanText = (reading) => (reading.spans || []).map(([a, b]) => raw.slice(a, b)).join(' ');
|
|
300
|
+
const refused = [];
|
|
274
301
|
let readings = [];
|
|
275
302
|
for (const c of list) {
|
|
276
|
-
|
|
303
|
+
// Rule 2: a confirmation is answered only when asked, firm by CONFIRM_WORDS, soft by the wider YES vocabulary.
|
|
304
|
+
const confirmation = ['firm', 'soft'].includes(c.confirmation) ? c.confirmation : '';
|
|
305
|
+
if (confirmation && !isCur(c)) {
|
|
306
|
+
if (readInputProfile(c.profile, raw, false))
|
|
307
|
+
refused.push({ form: c.form, name: c.name, rule: 'confirmation_out_of_order' });
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
// asked, or not a confirmation
|
|
312
|
+
}
|
|
313
|
+
const vocabulary = confirmation ? { ...c.profile, choices: (c.profile.choices || []).map((x) => x.key === 'yes' ? { ...x, phrases: [...(x.phrases ?? x.words ?? []), ...(confirmation === 'firm' ? CONFIRM_WORDS : ANSWER_WORDS.yes)], exact: confirmation === 'firm' || x.exact === true } : x.key === 'no' ? { ...x, phrases: [...(x.phrases ?? x.words ?? []), ...ANSWER_WORDS.no] } : x) } : c.profile;
|
|
314
|
+
const profile = narrativeCurrent && vocabulary.kind === 'email' ? { ...vocabulary, literal: true } : vocabulary;
|
|
277
315
|
let r = readInputProfile(profile, raw, isCur(c));
|
|
278
316
|
// A semantic choice supplied by ask-choice (e.g. spoken keypad keys) is a reading, not an early return.
|
|
279
317
|
if (!r && c === own && choiceKey && c.profile.kind === 'choice' && (c.profile.choices || []).some(x => x.key === choiceKey)) {
|
|
@@ -293,6 +331,42 @@ export function matchInputs(answer, candidates, current, choiceKey) {
|
|
|
293
331
|
}
|
|
294
332
|
}
|
|
295
333
|
}
|
|
334
|
+
// Context-aware acceptance (docs/copilot-reference.md §5; owner 2026-10-25): the standard rules for every form.
|
|
335
|
+
// The inputs the current question also asks for: its declared invites, or a read-back's (current names no input). An
|
|
336
|
+
// invite names an input of the asking form or of a form enclosing it; the nearest such form wins by scope.
|
|
337
|
+
const invitedNames = new Set([...list.filter(isCur).flatMap((c) => Array.isArray(c.invites) ? c.invites : []), ...(Array.isArray(cur.invites) ? cur.invites : [])]);
|
|
338
|
+
const invited = (reading) => !reading.current && invitedNames.has(reading.name);
|
|
339
|
+
// A declared confirm_against pair with the current question is relevant to it as well: the pair is asked about.
|
|
340
|
+
const pairedWithCurrent = (reading) => list.some((c) => isCur(c) && c.form === reading.form && ((c.confirm_against || []).includes(reading.name) || (fieldOf(reading).confirm_against || []).includes(c.name)));
|
|
341
|
+
const inOrder = (reading) => reading.current || invited(reading) || pairedWithCurrent(reading);
|
|
342
|
+
// Rule 3: a number read out of order must be whole: every digit run its evidence touches lies entirely inside it,
|
|
343
|
+
// so the last four of "88 91 7112" fill nothing.
|
|
344
|
+
const runs = [...raw.matchAll(/\d(?:[\s.-]?\d)*/g)].map((m) => [m.index, m.index + m[0].length]);
|
|
345
|
+
const wholeRuns = (reading) => runs.every(([a, b]) => !(reading.spans || []).some(([s, e]) => s < b && a < e) || (reading.spans || []).some(([s, e]) => s <= a && e >= b));
|
|
346
|
+
// Rule 4: a bare number has no money mark and no word in its evidence.
|
|
347
|
+
const bare = (reading) => !isAmountAnswer(raw) && (reading.spans || []).length > 0 && (reading.spans || []).every(([a, b]) => /\d/.test(raw.slice(a, b)) && !/\p{L}/u.test(raw.slice(a, b)));
|
|
348
|
+
readings = readings.filter((reading) => {
|
|
349
|
+
if (inOrder(reading)) {
|
|
350
|
+
if (!reading.current)
|
|
351
|
+
reading.rule = 'invited'; // the current input is the default: no rule recorded
|
|
352
|
+
else { /* the current input */ }
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
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
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
else if (/\d/.test(spanText(reading)) && !wholeRuns(reading)) {
|
|
360
|
+
refused.push({ form: reading.form, name: reading.name, value: reading.value, rule: 'fragment' });
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
reading.rule = 'out_of_order';
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
// Rule 4: a single digit the current question offers as a key is that key (the keypad reading wins its group below);
|
|
369
|
+
// one it does not offer reaches the invited amount, whose own validation decides ("3 dollars?").
|
|
296
370
|
const paired = (a, b) => a.confirm.indexOf(b.name) > -1 || b.confirm.indexOf(a.name) > -1;
|
|
297
371
|
// Narrative ownership protects ancestor choices and same-form choices that reset
|
|
298
372
|
// the requested field. Other sibling choices remain independent compound data. A current choice can carry narrative context too (e.g. a channel plus a
|
|
@@ -342,6 +416,7 @@ export function matchInputs(answer, candidates, current, choiceKey) {
|
|
|
342
416
|
effective.forEach((r, i) => { (groups[find(i)] = groups[find(i)] || []).push(r); });
|
|
343
417
|
const strip = ({ evidence, spans, keypad, fallback, repeatedChoice, wholeChoice, choice, narrative, resetOn, level, group, exclusive, current: c, confirm, ...r }) => r;
|
|
344
418
|
const hits = [];
|
|
419
|
+
const taken = [];
|
|
345
420
|
let ambiguous = [];
|
|
346
421
|
const remaining = [];
|
|
347
422
|
for (const group of Object.values(groups)) {
|
|
@@ -365,7 +440,7 @@ export function matchInputs(answer, candidates, current, choiceKey) {
|
|
|
365
440
|
const confirmWith = winners.length === 1 ? members.filter((m) => m !== winners[0] && paired(m, winners[0])) : [];
|
|
366
441
|
const asked = confirmWith.length ? winners.concat(confirmWith) : winners;
|
|
367
442
|
if (asked.length === 1) {
|
|
368
|
-
|
|
443
|
+
taken.push(asked[0]);
|
|
369
444
|
}
|
|
370
445
|
else if (ambiguous.length === 0) {
|
|
371
446
|
ambiguous = asked.map(strip);
|
|
@@ -374,5 +449,18 @@ export function matchInputs(answer, candidates, current, choiceKey) {
|
|
|
374
449
|
remaining.push(asked.map(strip));
|
|
375
450
|
}
|
|
376
451
|
}
|
|
377
|
-
|
|
452
|
+
// Rule 5: one part of an answer never fills two fields. A reading that shares evidence with another taken reading is
|
|
453
|
+
// refused with it, never kept by order.
|
|
454
|
+
const clash = (a, b) => (a.spans || []).some(([s, e]) => (b.spans || []).some(([t, u]) => s < u && t < e));
|
|
455
|
+
const kept = taken.filter((a) => !taken.some((b) => b !== a && clash(a, b)));
|
|
456
|
+
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
|
+
// Several fields from one answer: those filled out of order are a compound answer.
|
|
459
|
+
if (kept.length > 1)
|
|
460
|
+
kept.forEach((r) => { if (r.rule === 'out_of_order')
|
|
461
|
+
r.rule = 'compound'; });
|
|
462
|
+
hits.push(...kept.map(({ rule, ...r }) => strip(r)));
|
|
463
|
+
// Why each field was filled other than as the current answer, kept beside the hits so their shape never changes.
|
|
464
|
+
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 } : {}), ...(refused.length ? { refused } : {}), ...(decisions.length ? { decisions } : {}) };
|
|
378
466
|
}
|
package/dist/runtime-identity.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const JSFE_RUNTIME_FINGERPRINT = "sha256:
|
|
1
|
+
export const JSFE_RUNTIME_FINGERPRINT = "sha256:6f3a7201cfa214cba9a28fb392f8d61c4d731287b90a56b89cf191968d9edf56";
|
|
@@ -194,6 +194,44 @@ Matching depends on policy. With `exact:true`, a phrase must form the whole norm
|
|
|
194
194
|
|
|
195
195
|
JSFE resolves valid current-field evidence first, then a unique match in the nearest owning form, then ancestors; genuine competing claims are clarified. Independent spans can fill independent fields. Typed evidence outranks arbitrary text. `confirm_against` deliberately retains ambiguity. A child result uses the same acceptance/invalidation transition as a leaf. Pending dependent candidates wait for valid prerequisites; invalid data cannot authorize an action. Narrative mode does not turn fragments or bare structured identifiers into business explanations.
|
|
196
196
|
|
|
197
|
+
### Context-aware acceptance
|
|
198
|
+
|
|
199
|
+
A caller answers out of order, often several fields at once. That ability is kept, and every out-of-order answer is
|
|
200
|
+
validated against its context: an answer fills another question's field only where it is relevant there. These are the
|
|
201
|
+
standard rules for every form; the declarations below tell the engine what each field is.
|
|
202
|
+
|
|
203
|
+
Declarations:
|
|
204
|
+
|
|
205
|
+
| Declaration | Meaning |
|
|
206
|
+
| --- | --- |
|
|
207
|
+
| input `confirmation:"firm"` / `"soft"` | A yes/no decision. Firm (consent to a charge, a cancellation, storing a card, filing): YES only by `CONFIRM_WORDS` or the option's own exact phrases. Soft (an offer): the wider YES vocabulary. NO by the NO vocabulary or the option's own phrases. |
|
|
208
|
+
| input `invites:[names]`, `form.confirm.invites` | The other inputs this question (or the read-back) also asks for ("NEW CARD, LINK, or a different amount" invites `amount`), in its form or an enclosing one; read as in order while it is asked. |
|
|
209
|
+
| profile `out_of_order:{…}` | A narrower signature for out-of-order reading: the payment card takes a bare last four when asked, but out of order only a card word or "ending in 4242". |
|
|
210
|
+
| input `choices_from` | The choice's values come from prepared data (the caller's saved cards), so its signature is those values. |
|
|
211
|
+
| `form.compound:"worker"` | A parser that reads several of the form's fields from one answer (an address said in one breath). |
|
|
212
|
+
|
|
213
|
+
Rules, in order:
|
|
214
|
+
|
|
215
|
+
1. **The current question first.** The whole answer is read against the current input's signature and the inputs it
|
|
216
|
+
invites. A match is that answer.
|
|
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
|
+
option's own phrases, fill a confirmation that is not the current question.
|
|
219
|
+
3. **Out of order requires the whole signature.** A digit field takes a number only when the whole number fits its
|
|
220
|
+
length; a pattern only when it covers the whole number; a choice only its declared or prepared values. A fragment of
|
|
221
|
+
a longer number (the last four of "88 91 7112") fills nothing.
|
|
222
|
+
4. **A bare number** (no money mark, no word naming a field) is an amount only at a question that is, invites or is
|
|
223
|
+
declared `confirm_against` an amount. A single digit the question offers as a key is that key; one it does not offer reaches the invited amount,
|
|
224
|
+
whose validation decides (Curacao asks for it in dollars). A bare number that does not fit the current question is
|
|
225
|
+
read out of order under rule 3.
|
|
226
|
+
5. **Compound answers.** Several fields fill from one answer only through the form's compound parser, or when every
|
|
227
|
+
part of the answer fits exactly one field. **One part of an answer never fills two fields**; the engine refuses that
|
|
228
|
+
reading and records the refusal.
|
|
229
|
+
6. **Scope.** Across forms the nearest form wins; two fields of the same form that fit the same part are asked about,
|
|
230
|
+
never ranked.
|
|
231
|
+
|
|
232
|
+
Every result records why: `decisions` lists each field filled other than as the current answer with its rule (`invited`, `out_of_order`, `compound`), and every
|
|
233
|
+
refused reading is listed with its rule, so hosts can log what was filled out of order and what was refused.
|
|
234
|
+
|
|
197
235
|
## 6. Validation, reentry and contextual uncertainty
|
|
198
236
|
|
|
199
237
|
A field validator receives `{form,input,value,values,reading}` and writes `cargo.inputCheck={ok:true,value?}` or `{ok:false,message?,message_es?}`. Missing/malformed results fail closed. Activation values and conversational answers use the same validation path. `value` may normalize a valid input; never return success for a refusal to satisfy an effect's prerequisites.
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
## 1.10.0: context-aware acceptance
|
|
2
|
+
|
|
3
|
+
An answer given out of order fills another question's field only where it is relevant there (reference §5): a confirmation
|
|
4
|
+
(`confirmation:"firm"` or `"soft"` on a choice keyed yes and no) is answered only when asked, firm by the confirm words and
|
|
5
|
+
soft by the wider YES vocabulary; a bare number is an amount only at a question that is, invites (`invites`, also on a
|
|
6
|
+
read-back's `confirm`) or is paired (`confirm_against`) with one; a number read out of order must be whole, never the
|
|
7
|
+
fragment of a longer one; a profile may declare a narrower out-of-order signature (`out_of_order`); and one part of an
|
|
8
|
+
answer never fills two fields. `matchInputs` lists `refused` readings and the `decisions` behind out-of-order fills, and
|
|
9
|
+
the form workers append them to `cargo.formAcceptance` (`formAcceptanceTrace`, names and rules only) for the host to log.
|
|
10
|
+
At the code question, "Otra", "Número de cuenta" and "Account number" mean ANOTHER ACCOUNT.
|
|
11
|
+
|
|
12
|
+
## 1.9.4: card groups start like a card
|
|
13
|
+
|
|
14
|
+
`accumulate_first` (a regular expression) limits accumulation to a first group that matches it; the `card_number` preset
|
|
15
|
+
uses `^[2-6]` (every card network's first digit), so "1234" or "0729" said at the card question keeps its normal reading
|
|
16
|
+
as an expiration date or a security code.
|
|
17
|
+
|
|
1
18
|
## 1.9.3: a card number said in groups
|
|
2
19
|
|
|
3
20
|
A digit input may opt in to `accumulate: true`: an answer that is only digits and short of the input's minimum is kept, and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsfe",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
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",
|