jsfe 1.10.2 → 1.10.4
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/declarations.js +1 -1
- package/dist/forms/flows.d.ts +2 -1
- package/dist/forms/flows.d.ts.map +1 -1
- package/dist/forms/flows.js +24 -14
- package/dist/helpers/form-conversation.d.ts +4 -0
- package/dist/helpers/form-conversation.d.ts.map +1 -1
- package/dist/helpers/form-conversation.js +21 -0
- package/dist/helpers/form-state.d.ts +6 -1
- package/dist/helpers/form-state.d.ts.map +1 -1
- package/dist/helpers/form-state.js +24 -3
- package/dist/helpers/index.d.ts +5 -1
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/index.js +5 -5
- 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 +5 -1
- package/docs/development-release.md +15 -0
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ import { linkFormSchemas, validateFlowContracts } from './contracts.js';
|
|
|
4
4
|
import { compileFormPrompts } from './prompt-compiler.js';
|
|
5
5
|
import { withSafetyRevision } from './continuation-safety.js';
|
|
6
6
|
import { capturedAuthorSpec, copyAuthorSpec, authoredExpressions } from './declaration-capture.js';
|
|
7
|
-
const formKeys = new Set(['inputs', 'before', 'after', 'confirm', 'before_confirm', 'declined', 'stop', 'close_on_stop', 'resume_on_parent_change', 'done', 'validator', 'confirmation_condition', 'prompt', 'prompt_es', 'incomplete_outcome', 'on_exhausted']);
|
|
7
|
+
const formKeys = new Set(['inputs', 'before', 'after', 'confirm', 'before_confirm', 'declined', 'stop', 'close_on_stop', 'resume_on_parent_change', 'done', 'validator', 'confirmation_condition', 'prompt', 'prompt_es', 'incomplete_outcome', 'on_exhausted', 'must_complete']);
|
|
8
8
|
const sharedKeys = ['id', 'name', 'description', 'version', 'scope', 'primary', 'parameters', 'contract', 'parameterInstructions'];
|
|
9
9
|
const safe = /^[A-Za-z_$][\w$]*$/;
|
|
10
10
|
const fail = message => { throw new Error('Flow declaration: ' + message); };
|
package/dist/forms/flows.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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 }: {
|
|
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, must_complete }: {
|
|
2
2
|
id: any;
|
|
3
3
|
name: any;
|
|
4
4
|
version: any;
|
|
@@ -23,6 +23,7 @@ export function formFlow({ id, name, version, description, inputs, after, extraV
|
|
|
23
23
|
prompt_es?: undefined;
|
|
24
24
|
incomplete_outcome?: null | undefined;
|
|
25
25
|
on_exhausted?: null | undefined;
|
|
26
|
+
must_complete?: boolean | undefined;
|
|
26
27
|
}): {
|
|
27
28
|
variables: {
|
|
28
29
|
[k: string]: {
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4DC;AACD;;;GAGG;AACH;;;;;;EAGC;AAgZD,wHAAwH;AACxH,wEAaC;AACD,4FAA4F;AAC5F;;;EAYC;AACD,sGAAsG;AACtG;;;EAOC;AArkBM,6CAAuG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4vB9G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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.' },
|
|
@@ -111,8 +112,10 @@ function validateAcceptanceDeclarations(id, inputs) {
|
|
|
111
112
|
else { /* no invites, or a list of names */ }
|
|
112
113
|
}
|
|
113
114
|
}
|
|
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 }) {
|
|
115
|
+
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, must_complete = false }) {
|
|
115
116
|
validateFormExhaustion(on_exhausted);
|
|
117
|
+
if (typeof must_complete !== 'boolean')
|
|
118
|
+
throw new Error(`FORM ${id}: must_complete must be true or false`);
|
|
116
119
|
validateAcceptanceDeclarations(id, inputs);
|
|
117
120
|
const incompleteFrom = incomplete_outcome && Object.hasOwn(incomplete_outcome, 'from');
|
|
118
121
|
if (incompleteFrom) {
|
|
@@ -136,13 +139,13 @@ export function formFlow({ id, name, version, description, inputs, after = [], e
|
|
|
136
139
|
] : [];
|
|
137
140
|
return {
|
|
138
141
|
id, name, version, description, scope, metadata: { formProtocol: 2 }, parameters: [...parameters, { name: 'form_instance', type: 'string', internal: true, description: 'Internal instance to resume' }], ...(primary ? { primary: true } : {}), ...(prompt ? { prompt, prompt_es } : {}),
|
|
139
|
-
variables: vars({ form_on_exhausted: on_exhausted, form_incomplete_outcome: incompleteFrom ? null : incomplete_outcome, form_instance: '', form_internal_resume: false, form_resume_owner: '', form_arguments: {}, form_exhaustion: null, form_values: {}, form_validation: '', initial_readings: [], inputs: resolveFormInputs(inputs, FIELD_PRESETS), parent_form: '', result_input: '', questions: [], provides: null, exit_context: '', exit_context_es: '', form_confirm_q: '', form_confirm_q_es: '', form_confirmation: confirm || {}, form_should_confirm: false, ...extraVars }),
|
|
142
|
+
variables: vars({ form_on_exhausted: on_exhausted, form_must_complete: must_complete, form_incomplete_outcome: incompleteFrom ? null : incomplete_outcome, form_instance: '', form_internal_resume: false, form_resume_owner: '', form_arguments: {}, form_exhaustion: null, form_values: {}, form_validation: '', initial_readings: [], inputs: resolveFormInputs(inputs, FIELD_PRESETS), parent_form: '', result_input: '', questions: [], provides: null, exit_context: '', exit_context_es: '', form_confirm_q: '', form_confirm_q_es: '', form_confirmation: confirm || {}, form_should_confirm: false, ...extraVars }),
|
|
140
143
|
steps: [
|
|
141
144
|
...contextualResponseRuntimeSteps(id, [], hasFormResponse(inputs) ? [CONTEXTUAL_RESPONSES_REQUIREMENT] : []),
|
|
142
145
|
...(hasFormResponse(inputs) ? [{ ...SET(`${id}_response_reader_required`, 'noop', "typeof validateFormResponse === 'function'"), requireTrue: true }] : []),
|
|
143
146
|
...proposalRuntimeSteps(id, [], hasInputProposals(inputs) ? [INPUT_PROPOSALS_REQUIREMENT] : []),
|
|
144
147
|
SET(`${id}_resume_kind`, 'form_internal_resume', '!!form_instance'),
|
|
145
|
-
SET(`${id}_open`, 'form_instance', `openFormInstance(cargo, ${JSON.stringify(id)}, inputs, { parent: parent_form || null, input: result_input || '', context:!!parent_form && !result_input, resume_on_parent_change: ${resume_on_parent_change === true}, resume: form_instance || (!parent_form ? (cargo.formTasks || {})[${JSON.stringify(id)}] || '' : ''), providers: provides, questions: questions, incomplete_outcome: ${incompleteExpression}, on_exhausted: form_on_exhausted })`),
|
|
148
|
+
SET(`${id}_open`, 'form_instance', `openFormInstance(cargo, ${JSON.stringify(id)}, inputs, { parent: parent_form || null, input: result_input || '', context:!!parent_form && !result_input, resume_on_parent_change: ${resume_on_parent_change === true}, resume: form_instance || (!parent_form ? (cargo.formTasks || {})[${JSON.stringify(id)}] || '' : ''), providers: provides, questions: questions, incomplete_outcome: ${incompleteExpression}, on_exhausted: form_on_exhausted, must_complete: form_must_complete })`),
|
|
146
149
|
SIDE(`${id}_task`, `(!parent_form ? (cargo.formTasks = cargo.formTasks || {}, cargo.formTasks[${JSON.stringify(id)}] = form_instance) : true, true)`),
|
|
147
150
|
SIDE(`${id}_provider_reset`, '(delete cargo.forms[form_instance].providerRequests, delete cargo.forms[form_instance].activeGates, delete cargo.forms[form_instance].providerGateActive, true)'),
|
|
148
151
|
SIDE(`${id}_pass_reset`, '(cargo.forms[form_instance].replays = 0, form_internal_resume ? true : (cargo.forms[form_instance].attempts = {},cargo.forms[form_instance].misses={},cargo.forms[form_instance].feedback={}), true)'),
|
|
@@ -232,7 +235,7 @@ const collectInput = {
|
|
|
232
235
|
id: 'collect-input', name: 'CollectInput', version: '1.0.0', scope: 'isolated',
|
|
233
236
|
description: "Worker: ask the form's current input once and file the answer wherever it belongs in the form chain (matchInputs, decided by scope; a declared confirm_against pair is asked). One reading → collect-input-accept; ambiguous → disambiguate-input; a question about the form's data → provide-data; a short miss → counted, the loop re-asks; 3+ words that fit nothing → DISPATCH. Params: form, current, questions, provides, exit_context(_es).",
|
|
234
237
|
parameters: ['form', 'current', 'questions', 'provides', 'exit_context', 'exit_context_es'].map((n) => ({ name: n, type: ['questions'].includes(n) ? 'array' : n === 'provides' ? 'object' : 'string', description: n })),
|
|
235
|
-
variables: vars({ ci_partial: '', ci_exhaustion: null, ci_d: {}, ci_pre: '', ci_val: '', ci_routed: '', ci_names: [], ci_chain: [], ci_spec: null, ci_choices: [], ci_q: '', ci_q_es: '', ci_missed: 0, ci_max: 3, ci_misses: 0, ci_miss_limit: 2, ci_m: null, ci_qkey: '', ci_skip_gate: '', ci_words: 0, ci_digits: null, ci_amount: false, ci_as_spoken: false, ci_reader: {}, ci_pii: false, ci_ctx: '', ci_ctx_es: '' }),
|
|
238
|
+
variables: vars({ ci_partial: '', ci_exhaustion: null, ci_d: {}, ci_pre: '', ci_val: '', ci_routed: '', ci_names: [], ci_chain: [], ci_spec: null, ci_choices: [], ci_q: '', ci_q_es: '', ci_missed: 0, ci_max: 3, ci_misses: 0, ci_miss_limit: 2, ci_must: false, ci_m: null, ci_qkey: '', ci_skip_gate: '', ci_words: 0, ci_digits: null, ci_amount: false, ci_as_spoken: false, ci_reader: {}, ci_pii: false, ci_ctx: '', ci_ctx_es: '' }),
|
|
236
239
|
steps: [
|
|
237
240
|
SET('ci_find_spec', 'ci_spec', "(function () { var s = ((cargo.forms[form] || {}).specs || []).filter(function (s) { return s.name === current; })[0]; return s ? (Array.isArray(s.any_of) ? Object.assign({}, s, { profile: { kind: 'group' } }) : s) : null; })()"),
|
|
238
241
|
SET('ci_current_names', 'ci_names', "Array.isArray(ci_spec && ci_spec.any_of) ? ci_spec.any_of.map(function (m) { return m.name; }) : [current]"),
|
|
@@ -241,7 +244,9 @@ const collectInput = {
|
|
|
241
244
|
SET('ci_max_attempts', 'ci_max', 'Number(ci_spec.max_attempts || 3)'),
|
|
242
245
|
SET('ci_context_misses', 'ci_misses', 'Number(((cargo.forms[form] || {}).misses || {})[current] || 0)'),
|
|
243
246
|
SET('ci_context_limit', 'ci_miss_limit', 'Number(ci_spec.max_misses || 2)'),
|
|
244
|
-
|
|
247
|
+
// must_complete (the field's, else the nearest form's): an unreadable answer is never handed away; it is asked again
|
|
248
|
+
SET('ci_must_complete', 'ci_must', 'formMustComplete(cargo,form,current)'),
|
|
249
|
+
WHEN('ci_context_yield', 'ci_misses >= ci_miss_limit && !ci_must', yieldDispatch('ci_misses_dispatch', 'form', "'input_misses_exhausted'")),
|
|
245
250
|
SET('ci_context', 'ci_ctx', "typeof exit_context !== 'undefined' && exit_context ? exit_context : 'your request'"),
|
|
246
251
|
SET('ci_context_es', 'ci_ctx_es', "typeof exit_context_es !== 'undefined' && exit_context_es ? exit_context_es : 'su solicitud'"),
|
|
247
252
|
WHEN('ci_resolve_exhausted', 'ci_missed >= ci_max', SET('ci_exhausted_policy', 'ci_exhaustion', 'exhaustFormInput(cargo,form,current)')),
|
|
@@ -268,6 +273,7 @@ const collectInput = {
|
|
|
268
273
|
SET('ci_validator', 'ci_val', "ci_spec.answer_validator || ''"),
|
|
269
274
|
SET('ci_reader_context', 'ci_reader', '({form:form,current:current,names:ci_names,questions:provides && Array.isArray(questions)?questions:[]})'),
|
|
270
275
|
FLOW('ci_ask', 'ask-choice', 'call', { question: '{{ci_q}}', question_es: '{{ci_q_es}}', choices: '{{ci_choices}}', accept_other: true, digits: '{{ci_digits}}', amount: '{{ci_amount}}', as_spoken: '{{ci_as_spoken}}', pii: '{{ci_pii}}', exit_context: '{{ci_ctx}}', exit_context_es: '{{ci_ctx_es}}', form_context: '{{ci_reader}}', answer_precheck: '{{ci_pre}}', answer_validator: '{{ci_val}}' }),
|
|
276
|
+
SIDE('ci_answered', 'answerResetsFormReplays(cargo,form)'),
|
|
271
277
|
// a key the input routes to a handler of its own (route_keys: [{ prefix, flow }]): that flow takes the answer
|
|
272
278
|
SET('ci_route_to', 'ci_routed', "(ci_spec.route_keys || []).filter(function (r) { return String(cargo.choice || '').indexOf(r.prefix) === 0; }).map(function (r) { return r.flow; })[0] || ''"),
|
|
273
279
|
WHEN('ci_route_key', 'ci_routed', FLOW('ci_route_key_flow', '{{ci_routed}}', 'call', { form: '{{form}}', input: '{{current}}' })),
|
|
@@ -295,7 +301,7 @@ const collectInput = {
|
|
|
295
301
|
'condition: ci_m.hits.length > 0': FLOW('ci_accept', 'collect-input-accept', 'call', { hits: '{{ci_m.hits}}' }),
|
|
296
302
|
"condition: ci_m.questions.length > 0": END('ci_question_handled'),
|
|
297
303
|
"condition: ci_qkey && ci_qkey.indexOf('__') !== 0": FLOW('ci_question_asked', 'provide-data', 'call', { form: '{{form}}', provides: '{{provides}}', asked: '{{ci_qkey}}', no_menu: 'true', no_gate: '{{ci_skip_gate}}' }),
|
|
298
|
-
"condition: ci_words >= 3 || ci_spec.profile.kind === 'amount' && ci_words > 0": yieldDispatch('ci_elsewhere', 'form', "'input_not_matched'"),
|
|
304
|
+
"condition: !ci_must && (ci_words >= 3 || ci_spec.profile.kind === 'amount' && ci_words > 0)": yieldDispatch('ci_elsewhere', 'form', "'input_not_matched'"),
|
|
299
305
|
default: SIDE('ci_miss', "(missFormInput(cargo,form,current),true)")
|
|
300
306
|
} },
|
|
301
307
|
],
|
|
@@ -415,7 +421,7 @@ const disambiguate = {
|
|
|
415
421
|
id: 'disambiguate-input', name: 'DisambiguateInput', version: '1.0.0', scope: 'isolated',
|
|
416
422
|
description: "Worker: the answer fits two or more inputs at the same scope (or a declared confirm_against pair). Stores the unambiguous readings, asks which input the rest was for (keys 1..n, never 8), stores the pick. No pick: nothing stored, not a miss. Params: readings, hits.",
|
|
417
423
|
parameters: [{ name: 'readings', type: 'array', description: 'The ambiguous readings' }, { name: 'hits', type: 'array', description: 'Readings to store as they are' }, { name: 'remaining', type: 'array' }],
|
|
418
|
-
variables: vars({ di_form: '', di_input: '', di_prompt: '', di_prompt_es: '', di_words: 0, di_misses: 0, di_keys: [], di_choices: [], di_q: '', di_q_es: '', di_pick: null, di_value: '', remaining: [], di_next: [], di_rest: [] }),
|
|
424
|
+
variables: vars({ di_must: false, di_form: '', di_input: '', di_prompt: '', di_prompt_es: '', di_words: 0, di_misses: 0, di_keys: [], di_choices: [], di_q: '', di_q_es: '', di_pick: null, di_value: '', remaining: [], di_next: [], di_rest: [] }),
|
|
419
425
|
steps: [
|
|
420
426
|
WHEN('di_store_clear', 'Array.isArray(hits) && hits.length > 0', FLOW('di_store_hits', 'collect-input-accept', 'call', { hits: '{{hits}}' })),
|
|
421
427
|
SET('di_number', 'di_keys', `${KEYS_JS}(readings.length)`),
|
|
@@ -425,17 +431,19 @@ const disambiguate = {
|
|
|
425
431
|
SET('di_question', 'di_q', "(readings.every(function (r) { return r.option === true; }) ? 'Do you mean ' + readings.map(function (r) { return r.label; }).join(' or ') : (di_value ? 'Is ' + di_value + ' your ' : 'Is that your ') + readings.map(function (r) { return r.label; }).join(' or your ')) + '? ' + readings.map(function (r, i) { return (cargo.voice ? 'Press ' + di_keys[i] + ' or say ' : 'Say ') + r.label.toUpperCase(); }).join(', ') + '.'"),
|
|
426
432
|
SET('di_question_es', 'di_q_es', "(readings.every(function (r) { return r.option === true; }) ? '¿Se refiere a ' + readings.map(function (r) { return r.label_es; }).join(' o a ') : (di_value ? '¿' + di_value + ' es su ' : '¿Eso es su ') + readings.map(function (r) { return r.label_es; }).join(' o su ')) + '? ' + readings.map(function (r, i) { return (cargo.voice ? 'Presione ' + di_keys[i] + ' o diga ' : 'Diga ') + r.label_es.toUpperCase(); }).join(', ') + '.'"),
|
|
427
433
|
SET('di_owner', 'di_form', 'readings[0].form'),
|
|
434
|
+
SET('di_must_complete', 'di_must', 'readings.some(function(r){return formMustComplete(cargo,r.form,r.name);})'),
|
|
428
435
|
SET('di_counter', 'di_input', "'__ambiguity:'+readings.map(function(r){return r.form+':'+r.name;}).sort().join('|')"),
|
|
429
436
|
SET('di_prompt', 'di_prompt', "formInputPrompt(cargo,di_form,di_input,{question:di_q,question_es:di_q_es},{},'en')"),
|
|
430
437
|
SET('di_prompt_es', 'di_prompt_es', "formInputPrompt(cargo,di_form,di_input,{question:di_q,question_es:di_q_es},{},'es')"),
|
|
431
438
|
SIDE('di_feedback_consumed', 'consumeFormFeedback(cargo,di_form,di_input)'),
|
|
432
439
|
FLOW('di_ask', 'ask-choice', 'call', { question: '{{di_prompt}}', question_es: '{{di_prompt_es}}', choices: '{{di_choices}}', accept_other: true, digits: { min: 1, max: 1 } }),
|
|
440
|
+
SIDE('di_answered', 'answerResetsFormReplays(cargo,di_form)'),
|
|
433
441
|
SET('di_picked', 'di_pick', "/^r\\d+$/.test(String(cargo.choice || '')) ? [readings[Number(String(cargo.choice).slice(1))]] : null"),
|
|
434
442
|
WHEN('di_understood', 'di_pick', SIDE('di_clear_misses', 'clearFormMisses(cargo,di_form,di_input)')),
|
|
435
443
|
SET('di_word_count', 'di_words', "String(cargo.choiceText || '').split(/\\s+/).filter(function(w){return /[a-záéíóúüñ]/i.test(w);}).length"),
|
|
436
|
-
WHEN('di_context', '!di_pick && di_words >= 3', yieldDispatch('di_context_yield', 'di_form', "'ambiguity_not_resolved'")),
|
|
444
|
+
WHEN('di_context', '!di_pick && di_words >= 3 && !di_must', yieldDispatch('di_context_yield', 'di_form', "'ambiguity_not_resolved'")),
|
|
437
445
|
WHEN('di_miss', '!di_pick', SET('di_miss_count', 'di_misses', 'missFormInput(cargo,di_form,di_input)')),
|
|
438
|
-
WHEN('di_exhausted', '!di_pick && di_misses >= 2', yieldDispatch('di_misses_yield', 'di_form', "'ambiguity_misses_exhausted'")),
|
|
446
|
+
WHEN('di_exhausted', '!di_pick && di_misses >= 2 && !di_must', yieldDispatch('di_misses_yield', 'di_form', "'ambiguity_misses_exhausted'")),
|
|
439
447
|
WHEN('di_retry', '!di_pick', FLOW('di_retry_question', 'disambiguate-input', 'replace', { readings: '{{readings}}', hits: [], remaining: '{{remaining}}' })),
|
|
440
448
|
WHEN('di_store_pick', 'di_pick', FLOW('di_store_pick_flow', 'collect-input-accept', 'call', { hits: '{{di_pick}}' })),
|
|
441
449
|
SET('di_remaining_first', 'di_next', 'Array.isArray(remaining) && remaining.length ? remaining[0] : []'),
|
|
@@ -448,7 +456,7 @@ const confirmForm = {
|
|
|
448
456
|
id: 'confirm-form', name: 'ConfirmForm', version: '1.0.0', scope: 'isolated',
|
|
449
457
|
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).",
|
|
450
458
|
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: '' }),
|
|
459
|
+
variables: vars({ named_fields: false, cf_must: 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: '' }),
|
|
452
460
|
steps: [
|
|
453
461
|
SIDE('cf_reset', "(cargo.formConfirm = cargo.formConfirm || {}, cargo.formConfirm[form] = '', true)"),
|
|
454
462
|
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};}))`),
|
|
@@ -458,6 +466,7 @@ const confirmForm = {
|
|
|
458
466
|
SET('cf_prompt_es', 'cf_prompt_es', "formInputPrompt(cargo,form,'__confirm',{question:question,question_es:question_es},{},'es')"),
|
|
459
467
|
SIDE('cf_feedback_consumed', "consumeFormFeedback(cargo,form,'__confirm')"),
|
|
460
468
|
FLOW('cf_ask', 'ask-choice', 'call', { form_context: { form: '{{form}}', current: '__confirm' }, amount: '{{cf_amount}}', question: '{{cf_prompt}}', question_es: '{{cf_prompt_es}}', choices: '{{cf_choices}}', accept_other: true, reask_long: true, digits: { min: 1, max: 1 }, exit_context: '{{exit_context}}', exit_context_es: '{{exit_context_es}}' }),
|
|
469
|
+
SIDE('cf_answered', 'answerResetsFormReplays(cargo,form)'),
|
|
461
470
|
SET('cf_event', 'cf_event', "(Array.isArray(interrupts)?interrupts:[]).filter(function(event){return cargo.choice==='event:'+event.key;}).map(function(event){return event.flow;})[0] || ''"),
|
|
462
471
|
WHEN('cf_interrupt', 'cf_event', FLOW('cf_event_flow', '{{cf_event}}', 'reboot', { form: '{{form}}', event: '{{cargo.choice}}', text: '{{cargo.choiceText}}' })),
|
|
463
472
|
SET('cf_build_chain', 'cf_chain', CHAIN_JS),
|
|
@@ -475,6 +484,7 @@ const confirmForm = {
|
|
|
475
484
|
SET('cf_named_field', 'cf_named', "(named_fields === true || named_fields === 'true') && cargo.choice !== 'yes' && !cf_m.hits.length && !cf_m.ambiguous.length && !cf_m.questions.length ? (function (text, chain) { var plain = function (v) { return ' ' + String(v || '').toLowerCase().replace(/[^a-z0-9áéíóúüñ]+/g, ' ').trim() + ' '; }; var said = plain(text); var named = chain.filter(function (input) { return (input.names || []).some(function (name) { return said.indexOf(plain(name)) >= 0; }); }); return named.length === 1 ? { form: named[0].form, name: named[0].name } : null; })(cargo.choiceText, cf_chain) : null"),
|
|
476
485
|
WHEN('cf_question_fields', 'cf_m.questions.length > 0 && cf_m.hits.length > 0', FLOW('cf_question_accept', 'collect-input-accept', 'call', { hits: '{{cf_m.hits}}' })),
|
|
477
486
|
WHEN('cf_question_provide', 'cf_m.questions.length > 0', FLOW('cf_question_answer', 'provide-data', 'call', { form: '{{form}}', provides: '{{cf_provides}}', asked_keys: '{{cf_m.questions}}', no_menu: 'true', no_gate: '{{cf_skip_gate}}' })),
|
|
487
|
+
SET('cf_must_complete', 'cf_must', "formMustComplete(cargo,form,'__confirm')"),
|
|
478
488
|
{ id: 'cf_route', type: 'CASE', branches: {
|
|
479
489
|
'condition: cf_m.questions.length > 0': SET('cf_question_repeat', 'cf_result', "'changed'"),
|
|
480
490
|
"condition: cargo.choice === 'yes'": SET('cf_answered', 'cf_result', "'yes'"),
|
|
@@ -483,13 +493,13 @@ const confirmForm = {
|
|
|
483
493
|
'condition: cf_named': SET('cf_named_reset', 'cf_result', "(resetFormInputs(cargo, cf_named.form, [cf_named.name]), 'changed')"),
|
|
484
494
|
"condition: cargo.choice === 'no'": SET('cf_declined', 'cf_result', "'no'"),
|
|
485
495
|
"condition: Array.isArray(clarifications) && pickChoice(String(cargo.choiceText || '').replace(/[?¿]/g,''),[{key:'clarify',phrases:clarifications,exact:true}],false)==='clarify'": SET('cf_clarification', 'cf_result', "'clarification'"),
|
|
486
|
-
'condition: cf_words >= 3 || cf_amount && cf_words > 0': yieldDispatch('cf_elsewhere', 'form', "'confirmation_not_matched'"),
|
|
496
|
+
'condition: !cf_must && (cf_words >= 3 || cf_amount && cf_words > 0)': yieldDispatch('cf_elsewhere', 'form', "'confirmation_not_matched'"),
|
|
487
497
|
default: SET('cf_unclear', 'cf_result', "'unclear'")
|
|
488
498
|
} },
|
|
489
499
|
SET('cf_changed', 'cf_result', "cf_result ? cf_result : 'changed'"),
|
|
490
500
|
// an unclear short answer is asked again (the form runs again and reaches this question), at most twice
|
|
491
501
|
SET('cf_tries_count', 'cf_tries', "cf_result === 'unclear' ? missFormInput(cargo,form,'__confirm') : (clearFormMisses(cargo,form,'__confirm'),0)"),
|
|
492
|
-
WHEN('cf_misses_exhausted', 'cf_tries >= 2', yieldDispatch('cf_misses_dispatch', 'form', "'confirmation_misses_exhausted'")),
|
|
502
|
+
WHEN('cf_misses_exhausted', 'cf_tries >= 2 && !cf_must', yieldDispatch('cf_misses_dispatch', 'form', "'confirmation_misses_exhausted'")),
|
|
493
503
|
WHEN('cf_bind_revision', "cf_result === 'yes'", SIDE('cf_bind', 'confirmFormRevision(cargo, form)')),
|
|
494
504
|
SIDE('cf_store', "(cargo.formConfirm[form] = cf_result === 'unclear' || cf_result === 'clarification' ? 'changed' : cf_result, true)"),
|
|
495
505
|
],
|
|
@@ -537,7 +547,7 @@ const validators = [
|
|
|
537
547
|
{ id: 'validate-card-expiration', name: 'ValidateCardExpiration', version: '1.0.0', scope: 'isolated',
|
|
538
548
|
description: 'Validator: month and year, a real month, not expired (validateExpiration). Normalizes to MMYY. Params: input, value.',
|
|
539
549
|
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)")] },
|
|
550
|
+
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
551
|
];
|
|
542
552
|
// ── field collectors and sub-forms ──────────────────────────────────────────────────────────────────────────────────
|
|
543
553
|
const fieldCollectors = Object.entries(FIELD_COLLECTORS).map(([id, field]) => formFlow({
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export function clearFormMisses(cargo: any, id: any, input: any): boolean;
|
|
2
2
|
export function missFormInput(cargo: any, id: any, input: any): any;
|
|
3
|
+
/** must_complete: the field's own declaration (true or false) wins, else any enclosing form that declares it. Such an input never hands the
|
|
4
|
+
* turn away on an unreadable answer: it says it did not catch that and asks again. Its exits stay explicit (the form's
|
|
5
|
+
* stop words, max_attempts and on_exhausted, and whatever the host intercepts before the engine). */
|
|
6
|
+
export function formMustComplete(cargo: any, id: any, input: any): boolean;
|
|
3
7
|
/** Explicit retry wins. Otherwise acknowledge only an actual unrecognized answer, once per re-ask. */
|
|
4
8
|
export function formInputPrompt(cargo: any, id: any, input: any, spec: any, data?: {}, language?: string): any;
|
|
5
9
|
export function consumeFormFeedback(cargo: any, id: any, input: any): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-conversation.d.ts","sourceRoot":"","sources":["../../src/helpers/form-conversation.js"],"names":[],"mappings":"AAGA,0EAGC;AACD,oEAGC;AACD,sGAAsG;AACtG,+GAOC;AACD,8EAAwI;AAExI,yGAAyG;AACzG;;;;;;;;;;;;;;;;;;;;EAUC;AAED,sGAAsG;AACtG;;;;;;;;;;;;;;;;;;;;EAeC"}
|
|
1
|
+
{"version":3,"file":"form-conversation.d.ts","sourceRoot":"","sources":["../../src/helpers/form-conversation.js"],"names":[],"mappings":"AAGA,0EAGC;AACD,oEAGC;AACD;;qGAEqG;AACrG,2EAWC;AACD,sGAAsG;AACtG,+GAOC;AACD,8EAAwI;AAExI,yGAAyG;AACzG;;;;;;;;;;;;;;;;;;;;EAUC;AAED,sGAAsG;AACtG;;;;;;;;;;;;;;;;;;;;EAeC"}
|
|
@@ -20,6 +20,27 @@ export function missFormInput(cargo, id, input) {
|
|
|
20
20
|
state.feedback[input] = 'miss';
|
|
21
21
|
return state.misses[input];
|
|
22
22
|
}
|
|
23
|
+
/** must_complete: the field's own declaration (true or false) wins, else any enclosing form that declares it. Such an input never hands the
|
|
24
|
+
* turn away on an unreadable answer: it says it did not catch that and asks again. Its exits stay explicit (the form's
|
|
25
|
+
* stop words, max_attempts and on_exhausted, and whatever the host intercepts before the engine). */
|
|
26
|
+
export function formMustComplete(cargo, id, input) {
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
let name = input;
|
|
29
|
+
while (id && cargo.forms?.[id] && !seen.has(id)) {
|
|
30
|
+
seen.add(id);
|
|
31
|
+
const state = cargo.forms[id];
|
|
32
|
+
const group = (state.specs || []).find(s => s.name === name || s.any_of?.some(i => i.name === name));
|
|
33
|
+
const member = group?.any_of?.find(i => i.name === name);
|
|
34
|
+
for (const declared of [member?.must_complete, group?.must_complete])
|
|
35
|
+
if (typeof declared === 'boolean')
|
|
36
|
+
return declared;
|
|
37
|
+
if (state.must_complete === true)
|
|
38
|
+
return true;
|
|
39
|
+
name = state.result_input;
|
|
40
|
+
id = state.parent;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
23
44
|
/** Explicit retry wins. Otherwise acknowledge only an actual unrecognized answer, once per re-ask. */
|
|
24
45
|
export function formInputPrompt(cargo, id, input, spec, data = {}, language = 'en') {
|
|
25
46
|
const es = language === 'es', feedback = cargo.forms?.[id]?.feedback?.[input];
|
|
@@ -18,7 +18,7 @@ export function settleFormActivations(batch?: {}, cargo?: null): {
|
|
|
18
18
|
pending: any[];
|
|
19
19
|
};
|
|
20
20
|
/** Explicit ownership: unrelated tasks and sibling inputs never share an instance by definition id. */
|
|
21
|
-
export function openFormInstance(cargo: any, definition: any, specs: any, { parent, input, resume, task, providers, questions, context, resume_on_parent_change, incomplete_outcome, on_exhausted }?: {
|
|
21
|
+
export function openFormInstance(cargo: any, definition: any, specs: any, { parent, input, resume, task, providers, questions, context, resume_on_parent_change, incomplete_outcome, on_exhausted, must_complete }?: {
|
|
22
22
|
parent?: null | undefined;
|
|
23
23
|
input?: string | undefined;
|
|
24
24
|
resume?: string | undefined;
|
|
@@ -29,6 +29,7 @@ export function openFormInstance(cargo: any, definition: any, specs: any, { pare
|
|
|
29
29
|
resume_on_parent_change?: undefined;
|
|
30
30
|
incomplete_outcome?: undefined;
|
|
31
31
|
on_exhausted?: undefined;
|
|
32
|
+
must_complete?: undefined;
|
|
32
33
|
}): any;
|
|
33
34
|
/** An incomplete optional follow-up may preserve an explicitly completed task outcome. */
|
|
34
35
|
export function validateFormIncompleteOutcome(value: any): {
|
|
@@ -82,6 +83,10 @@ export function formPreparation(cargo: any, id: any, input: any): {
|
|
|
82
83
|
} | null;
|
|
83
84
|
export function queueFormReadings(cargo: any, hits?: any[]): boolean;
|
|
84
85
|
export function pendingFormReadings(cargo: any, id: any): any[];
|
|
86
|
+
/** The caller answered: the runaway guard (replays > formWorkLimit) counts passes WITHOUT an answer, which is what an
|
|
87
|
+
* internal loop is, so an answer resets it on the answering form and every ancestor. Counting answers too ended a form
|
|
88
|
+
* whose caller simply needed several tries (a must_complete field re-asks every unreadable answer). */
|
|
89
|
+
export function answerResetsFormReplays(cargo: any, id: any): boolean;
|
|
85
90
|
export function formWorkLimit(cargo: any, id: any): number;
|
|
86
91
|
export function applyFormValidation(cargo: any, id: any, result: any): "failed" | "valid" | "retry";
|
|
87
92
|
/** Invalidation is transitive and independent of declaration order. */
|
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"form-state.d.ts","sourceRoot":"","sources":["../../src/helpers/form-state.js"],"names":[],"mappings":"AAQA,qDAKC;AAED,oDAEC;AAED,qFAAqF;AACrF,oEA8BC;AAuCD,2FAEC;AACD,8FAcC;AACD,2DAOC;AACD,uEA0CC;AAED,kGAAkG;AAClG,2GAOC;AACD,0HAA0H;AAC1H,uEAwBC;AACD,8HAA8H;AAC9H;;;;;EAWC;AACD,uGAAuG;AACvG;;;;;;;;;;;;QAkCC;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;;uGAEuG;AACvG,sEAGC;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;AA7oBM,8CAAmE"}
|
|
@@ -49,6 +49,8 @@ export function resolveFormInputs(inputs, presets = {}) {
|
|
|
49
49
|
throw new Error('FORM proposal requires a readable leaf input; declare it on each group member or inside the subform');
|
|
50
50
|
validateFormProposalPolicy(input.proposal);
|
|
51
51
|
}
|
|
52
|
+
if (input.must_complete !== undefined && typeof input.must_complete !== 'boolean')
|
|
53
|
+
throw new Error('FORM must_complete must be true or false');
|
|
52
54
|
if (input.max_misses !== undefined && (!Number.isInteger(input.max_misses) || input.max_misses < 1))
|
|
53
55
|
throw new Error('FORM max_misses must be a positive integer');
|
|
54
56
|
if (input.max_attempts !== undefined && (!Number.isInteger(input.max_attempts) || input.max_attempts < 1))
|
|
@@ -190,7 +192,10 @@ export function formInputChain(forms, id, cargo = {}) {
|
|
|
190
192
|
out.push({ ...input,
|
|
191
193
|
...(prefix ? { label: `${prefix}: ${input.label || input.name}`, label_es: `${prefixEs || prefix}: ${input.label_es || input.label || input.name}` } : {}),
|
|
192
194
|
...(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]
|
|
195
|
+
...(activation ? { activation } : {}), form: target, scope, ...(spec.any_of ? { group: spec.name, exclusive: spec.exclusive === true } : {}), value: state.values[input.name],
|
|
196
|
+
// its declared prerequisites hold accepted values: a field that cannot be validated yet is not a context for
|
|
197
|
+
// an unnamed out-of-order answer (contract, reference §5)
|
|
198
|
+
ready: (input.depends_on || spec.depends_on || []).every(name => hasFormValue(state.values[name])) });
|
|
194
199
|
}
|
|
195
200
|
}
|
|
196
201
|
};
|
|
@@ -282,7 +287,7 @@ export function settleFormActivations(batch = {}, cargo = null) {
|
|
|
282
287
|
rejected: rejected.map(h => h.activation_inferred ? { ...h, ...h.activation_entry, activation_inferred: false } : h), pending: pending.concat(accepted.filter(h => held.has(h.activation_group))).filter(h => !discarded.has(h.activation_group)).map(h => held.has(h.activation_group) ? { ...h, ok: undefined, outcome: 'pending', silent: true } : h) };
|
|
283
288
|
}
|
|
284
289
|
/** Explicit ownership: unrelated tasks and sibling inputs never share an instance by definition id. */
|
|
285
|
-
export function openFormInstance(cargo, definition, specs, { parent = null, input = '', resume = '', task = '', providers = null, questions = null, context = false, resume_on_parent_change = undefined, incomplete_outcome = undefined, on_exhausted = undefined } = {}) {
|
|
290
|
+
export function openFormInstance(cargo, definition, specs, { parent = null, input = '', resume = '', task = '', providers = null, questions = null, context = false, resume_on_parent_change = undefined, incomplete_outcome = undefined, on_exhausted = undefined, must_complete = undefined } = {}) {
|
|
286
291
|
const incomplete = validateFormIncompleteOutcome(incomplete_outcome);
|
|
287
292
|
validateFormExhaustion(on_exhausted);
|
|
288
293
|
cargo.forms ||= {};
|
|
@@ -298,6 +303,8 @@ export function openFormInstance(cargo, definition, specs, { parent = null, inpu
|
|
|
298
303
|
existing.incomplete_outcome = incomplete;
|
|
299
304
|
if (on_exhausted !== undefined)
|
|
300
305
|
existing.on_exhausted = on_exhausted;
|
|
306
|
+
if (must_complete !== undefined)
|
|
307
|
+
existing.must_complete = must_complete === true;
|
|
301
308
|
return resume;
|
|
302
309
|
}
|
|
303
310
|
if (parent && !cargo.forms[parent])
|
|
@@ -314,13 +321,15 @@ export function openFormInstance(cargo, definition, specs, { parent = null, inpu
|
|
|
314
321
|
cargo.forms[child].incomplete_outcome = incomplete;
|
|
315
322
|
if (on_exhausted !== undefined)
|
|
316
323
|
cargo.forms[child].on_exhausted = on_exhausted;
|
|
324
|
+
if (must_complete !== undefined)
|
|
325
|
+
cargo.forms[child].must_complete = must_complete === true;
|
|
317
326
|
return child;
|
|
318
327
|
}
|
|
319
328
|
}
|
|
320
329
|
cargo.formSequence = Number(cargo.formSequence || 0) + 1;
|
|
321
330
|
const id = `${definition}:${cargo.formSequence}`;
|
|
322
331
|
cargo.forms[id] = { id, definition, task: owner?.task || task || id, parent, result_input: input,
|
|
323
|
-
on_exhausted: on_exhausted || '', incomplete_outcome: incomplete, context_only: !!context, resume_on_parent_change: resume_on_parent_change === true, parent_revision: owner?.revision ?? null, specs, values: {}, accepted: {}, errors: {}, pending: {}, seeds: {}, attempts: {}, misses: {}, feedback: {}, prepared: {}, data: {}, arguments: {}, contexts: {}, children: {}, childResults: {}, revision: 0,
|
|
332
|
+
on_exhausted: on_exhausted || '', must_complete: must_complete === true, incomplete_outcome: incomplete, context_only: !!context, resume_on_parent_change: resume_on_parent_change === true, parent_revision: owner?.revision ?? null, specs, values: {}, accepted: {}, errors: {}, pending: {}, seeds: {}, attempts: {}, misses: {}, feedback: {}, prepared: {}, data: {}, arguments: {}, contexts: {}, children: {}, childResults: {}, revision: 0,
|
|
324
333
|
providers: providers || owner?.providers || null, questions: questions?.length ? questions : owner?.questions || [], status: 'collecting' };
|
|
325
334
|
if (owner) {
|
|
326
335
|
if (context) {
|
|
@@ -705,6 +714,18 @@ export function pendingFormReadings(cargo, id) {
|
|
|
705
714
|
}
|
|
706
715
|
return orderFormReadings(cargo, out);
|
|
707
716
|
}
|
|
717
|
+
/** The caller answered: the runaway guard (replays > formWorkLimit) counts passes WITHOUT an answer, which is what an
|
|
718
|
+
* internal loop is, so an answer resets it on the answering form and every ancestor. Counting answers too ended a form
|
|
719
|
+
* whose caller simply needed several tries (a must_complete field re-asks every unreadable answer). */
|
|
720
|
+
export function answerResetsFormReplays(cargo, id) {
|
|
721
|
+
const seen = new Set();
|
|
722
|
+
while (id && cargo.forms?.[id] && !seen.has(id)) {
|
|
723
|
+
seen.add(id);
|
|
724
|
+
cargo.forms[id].replays = 0;
|
|
725
|
+
id = cargo.forms[id].parent;
|
|
726
|
+
}
|
|
727
|
+
return true;
|
|
728
|
+
}
|
|
708
729
|
export function formWorkLimit(cargo, id) {
|
|
709
730
|
const ancestors = new Set();
|
|
710
731
|
while (cargo.forms?.[id]?.parent && !ancestors.has(id)) {
|
package/dist/helpers/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export namespace STANDARD_FUNCTIONS {
|
|
|
19
19
|
export { formProposalDescriptor };
|
|
20
20
|
export { clearFormMisses };
|
|
21
21
|
export { missFormInput };
|
|
22
|
+
export { formMustComplete };
|
|
22
23
|
export { formInputPrompt };
|
|
23
24
|
export { consumeFormFeedback };
|
|
24
25
|
export { formConversationOutcome };
|
|
@@ -102,6 +103,7 @@ export namespace STANDARD_FUNCTIONS {
|
|
|
102
103
|
export { queueFormReadings };
|
|
103
104
|
export { pendingFormReadings };
|
|
104
105
|
export { formWorkLimit };
|
|
106
|
+
export { answerResetsFormReplays };
|
|
105
107
|
export { rejectFormReadings };
|
|
106
108
|
export { invalidateFormInputs };
|
|
107
109
|
export { acceptFormReadings };
|
|
@@ -134,6 +136,7 @@ import { validateFormProposalDescriptor } from './proposal-contract.js';
|
|
|
134
136
|
import { formProposalDescriptor } from './proposal-contract.js';
|
|
135
137
|
import { clearFormMisses } from './form-conversation.js';
|
|
136
138
|
import { missFormInput } from './form-conversation.js';
|
|
139
|
+
import { formMustComplete } from './form-conversation.js';
|
|
137
140
|
import { formInputPrompt } from './form-conversation.js';
|
|
138
141
|
import { consumeFormFeedback } from './form-conversation.js';
|
|
139
142
|
import { formConversationOutcome } from './form-conversation.js';
|
|
@@ -219,6 +222,7 @@ import { formPreparation } from './form-state.js';
|
|
|
219
222
|
import { queueFormReadings } from './form-state.js';
|
|
220
223
|
import { pendingFormReadings } from './form-state.js';
|
|
221
224
|
import { formWorkLimit } from './form-state.js';
|
|
225
|
+
import { answerResetsFormReplays } from './form-state.js';
|
|
222
226
|
import { rejectFormReadings } from './form-state.js';
|
|
223
227
|
import { invalidateFormInputs } from './form-state.js';
|
|
224
228
|
import { acceptFormReadings } from './form-state.js';
|
|
@@ -234,5 +238,5 @@ import { validatePhone } from './validators.js';
|
|
|
234
238
|
import { validateEmail } from './validators.js';
|
|
235
239
|
import { urlToSpeech } from './url-speech.js';
|
|
236
240
|
import { textWithUrlToSpeech } from './url-speech.js';
|
|
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 };
|
|
241
|
+
export { continuationSafetyProtocol, cancelFormTask, contextualResponseProtocol, validateFormResponse, hasFormResponse, formProposalPolicy, stageFormProposal, nextFormProposal, formProposalReadings, bindFormProposal, decideFormProposal, commitFormProposals, settleFormProposalValidation, formProposalProtocol, validateFormProposalPolicy, validateFormProposalDescriptor, formProposalDescriptor, clearFormMisses, missFormInput, formMustComplete, 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, answerResetsFormReplays, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, formParentResume, closeFormInstance, checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech };
|
|
238
242
|
//# 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;+BAI8D,wBAAwB;2CAHlF,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;gCACJ,wBAAwB;8BAAxB,wBAAwB;iCAAxB,wBAAwB;gCAAxB,wBAAwB;oCAAxB,wBAAwB;wCAAxB,wBAAwB;gCAmBjC,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;kCACkoB,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;wCAAjB,iBAAiB;mCAAjB,iBAAiB;qCAAjB,iBAAiB;mCAAjB,iBAAiB;oCAAjB,iBAAiB;iCAAjB,iBAAiB;kCAAjB,iBAAiB;yBACnrB,gBAAgB;gCAAhB,gBAAgB;4BAAhB,gBAAgB;8BAAhB,gBAAgB;+BACzB,iBAAiB;8BAAjB,iBAAiB;8BAAjB,iBAAiB;4BAC7B,iBAAiB;oCAAjB,iBAAiB"}
|
package/dist/helpers/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { continuationSafetyProtocol } from '../forms/continuation-safety.js';
|
|
|
3
3
|
import { contextualResponseProtocol, validateFormResponse, hasFormResponse } from './contextual-response.js';
|
|
4
4
|
import { formProposalPolicy, stageFormProposal, nextFormProposal, formProposalReadings, bindFormProposal, decideFormProposal, commitFormProposals, settleFormProposalValidation } from './form-proposals.js';
|
|
5
5
|
import { formProposalProtocol, validateFormProposalPolicy, validateFormProposalDescriptor, formProposalDescriptor } from './proposal-contract.js';
|
|
6
|
-
import { cancelFormTask, clearFormMisses, missFormInput, formInputPrompt, consumeFormFeedback, formConversationOutcome } from './form-conversation.js';
|
|
6
|
+
import { cancelFormTask, clearFormMisses, missFormInput, formMustComplete, formInputPrompt, consumeFormFeedback, formConversationOutcome } from './form-conversation.js';
|
|
7
7
|
import { takeChoiceOffer } from './choice-offer.js';
|
|
8
8
|
import { consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFormAction, confirmFormRevision, prepareFormAction, actionConfirmationValid, beginFormAction, finishFormAction, actionGateVerified } from './form-actions.js';
|
|
9
9
|
/**
|
|
@@ -31,18 +31,18 @@ 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
33
|
import { matchInputs, readFormAnswer, readInputProfile, formAcceptanceTrace } from './match-inputs.js';
|
|
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';
|
|
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, answerResetsFormReplays, 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, 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, };
|
|
38
|
+
export { continuationSafetyProtocol, cancelFormTask, contextualResponseProtocol, validateFormResponse, hasFormResponse, formProposalPolicy, stageFormProposal, nextFormProposal, formProposalReadings, bindFormProposal, decideFormProposal, commitFormProposals, settleFormProposalValidation, formProposalProtocol, validateFormProposalPolicy, validateFormProposalDescriptor, formProposalDescriptor, clearFormMisses, missFormInput, formMustComplete, 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, answerResetsFormReplays, 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,
|
|
42
42
|
formProposalPolicy, stageFormProposal, nextFormProposal, formProposalReadings, bindFormProposal, decideFormProposal, commitFormProposals, settleFormProposalValidation, formProposalProtocol, validateFormProposalPolicy, validateFormProposalDescriptor, formProposalDescriptor,
|
|
43
|
-
clearFormMisses, missFormInput, formInputPrompt, consumeFormFeedback, formConversationOutcome,
|
|
43
|
+
clearFormMisses, missFormInput, formMustComplete, 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, 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,
|
|
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, answerResetsFormReplays, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, formParentResume, closeFormInstance,
|
|
47
47
|
validateOTP, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech,
|
|
48
48
|
};
|
|
@@ -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:32e5bd91d0c35d60c3126334ae4d931bd988af19be6acd7fcdb9023794e61840";
|
|
@@ -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
|
|
@@ -246,6 +248,8 @@ The loop skips accepted data and asks the first ready missing required field. Co
|
|
|
246
248
|
|
|
247
249
|
`max_misses` defaults to two consecutive unreadable answers: clarify the first with explicit retry text or a localized “Sorry, I didn't catch that.” prefix; yield the second utterance to DISPATCH while preserving the draft. Deterministic uncertainty may yield earlier, especially unrecognized monetary instructions. Validator failures use a separate `max_attempts` budget. An out-of-order accepted answer is not a miss. On exhaustion the nearest declared `on_exhausted` receives `{form,input,failed_form,failed_input,reason:"attempts_exhausted"}` and must deliberately recover that branch; it does not receive consent.
|
|
248
250
|
|
|
251
|
+
`must_complete:true` on a form (inherited by every field of it and of the forms nested in it) or on one field (`false` on a field opts out): an unreadable answer is never yielded to DISPATCH, however long, and neither is a confirmation miss or an unresolved ambiguity — the question is asked again, acknowledged. Use it where leaving would strand an effect in progress (a payment's card, its billing-ZIP correction). Exits stay explicit: the form's stop words, `max_attempts` with `on_exhausted`, and whatever the host intercepts before the engine. The form runaway guard counts passes without a caller answer only.
|
|
252
|
+
|
|
249
253
|
Use `response:{description}` for open descriptions that need the existing intent/extraction model's interpretation. The prepared question and field description become contextual DISPATCH context. An extracted answer still passes deterministic validation; missing usable data yields with progress saved. A supported different intent routes to that flow; otherwise the host can answer with its full context. No separate field classifier/model role is needed. Do not attempt exhaustive conversational inference with keyword lists.
|
|
250
254
|
|
|
251
255
|
Ordinary DISPATCH is the default uncertainty escape. An explicit contextual DISPATCH may additionally declare the following step fragment (its surrounding flow must declare the response object and supply its own input/recovery steps):
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
## 1.10.4: forms that must complete
|
|
2
|
+
|
|
3
|
+
`must_complete:true` on a form or a field (a field inherits it from any enclosing form; `false` on a field opts out): an
|
|
4
|
+
unreadable answer, long or short, a confirmation miss and an unresolved ambiguity are asked again instead of yielding to
|
|
5
|
+
DISPATCH (reference, "Retry, yield and recovery"). Prod 2026-10-25: "el número postal el código postal" at the billing-ZIP
|
|
6
|
+
correction ended the payment. The form runaway guard now counts passes since the caller's last answer, so a caller who
|
|
7
|
+
needs several tries is not mistaken for a loop.
|
|
8
|
+
|
|
9
|
+
## 1.10.3: a field is ready before an unnamed answer fills it
|
|
10
|
+
|
|
11
|
+
A bare number never fills, out of order, a field whose `depends_on` prerequisites are not filled yet (`formInputChain`
|
|
12
|
+
marks each candidate `ready`; refusal rule `prerequisite_unmet`): an account number keyed in pieces is no longer taken for
|
|
13
|
+
the payment's sub-account before the account is known. A named answer still waits as a pending value. The shared card
|
|
14
|
+
expiration also reads a zero said between month and year ("020 28" as 02/28).
|
|
15
|
+
|
|
1
16
|
## 1.10.2: "account number" at the contact question
|
|
2
17
|
|
|
3
18
|
At the shared contact question (`get-cell-or-email`), a caller who names the account number without giving it yet ("número
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsfe",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.4",
|
|
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",
|