jsfe 1.10.3 → 1.10.5
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 +21 -12
- package/dist/helpers/answer.d.ts.map +1 -1
- package/dist/helpers/answer.js +7 -1
- 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 +20 -2
- 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/runtime-identity.js +1 -1
- package/docs/copilot-reference.md +2 -0
- package/docs/development-release.md +14 -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":"AAsHA
|
|
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
|
@@ -112,8 +112,10 @@ function validateAcceptanceDeclarations(id, inputs) {
|
|
|
112
112
|
else { /* no invites, or a list of names */ }
|
|
113
113
|
}
|
|
114
114
|
}
|
|
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 }) {
|
|
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 }) {
|
|
116
116
|
validateFormExhaustion(on_exhausted);
|
|
117
|
+
if (typeof must_complete !== 'boolean')
|
|
118
|
+
throw new Error(`FORM ${id}: must_complete must be true or false`);
|
|
117
119
|
validateAcceptanceDeclarations(id, inputs);
|
|
118
120
|
const incompleteFrom = incomplete_outcome && Object.hasOwn(incomplete_outcome, 'from');
|
|
119
121
|
if (incompleteFrom) {
|
|
@@ -137,13 +139,13 @@ export function formFlow({ id, name, version, description, inputs, after = [], e
|
|
|
137
139
|
] : [];
|
|
138
140
|
return {
|
|
139
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 } : {}),
|
|
140
|
-
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 }),
|
|
141
143
|
steps: [
|
|
142
144
|
...contextualResponseRuntimeSteps(id, [], hasFormResponse(inputs) ? [CONTEXTUAL_RESPONSES_REQUIREMENT] : []),
|
|
143
145
|
...(hasFormResponse(inputs) ? [{ ...SET(`${id}_response_reader_required`, 'noop', "typeof validateFormResponse === 'function'"), requireTrue: true }] : []),
|
|
144
146
|
...proposalRuntimeSteps(id, [], hasInputProposals(inputs) ? [INPUT_PROPOSALS_REQUIREMENT] : []),
|
|
145
147
|
SET(`${id}_resume_kind`, 'form_internal_resume', '!!form_instance'),
|
|
146
|
-
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 })`),
|
|
147
149
|
SIDE(`${id}_task`, `(!parent_form ? (cargo.formTasks = cargo.formTasks || {}, cargo.formTasks[${JSON.stringify(id)}] = form_instance) : true, true)`),
|
|
148
150
|
SIDE(`${id}_provider_reset`, '(delete cargo.forms[form_instance].providerRequests, delete cargo.forms[form_instance].activeGates, delete cargo.forms[form_instance].providerGateActive, true)'),
|
|
149
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)'),
|
|
@@ -233,7 +235,7 @@ const collectInput = {
|
|
|
233
235
|
id: 'collect-input', name: 'CollectInput', version: '1.0.0', scope: 'isolated',
|
|
234
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).",
|
|
235
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 })),
|
|
236
|
-
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: '' }),
|
|
237
239
|
steps: [
|
|
238
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; })()"),
|
|
239
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]"),
|
|
@@ -242,7 +244,9 @@ const collectInput = {
|
|
|
242
244
|
SET('ci_max_attempts', 'ci_max', 'Number(ci_spec.max_attempts || 3)'),
|
|
243
245
|
SET('ci_context_misses', 'ci_misses', 'Number(((cargo.forms[form] || {}).misses || {})[current] || 0)'),
|
|
244
246
|
SET('ci_context_limit', 'ci_miss_limit', 'Number(ci_spec.max_misses || 2)'),
|
|
245
|
-
|
|
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'")),
|
|
246
250
|
SET('ci_context', 'ci_ctx', "typeof exit_context !== 'undefined' && exit_context ? exit_context : 'your request'"),
|
|
247
251
|
SET('ci_context_es', 'ci_ctx_es', "typeof exit_context_es !== 'undefined' && exit_context_es ? exit_context_es : 'su solicitud'"),
|
|
248
252
|
WHEN('ci_resolve_exhausted', 'ci_missed >= ci_max', SET('ci_exhausted_policy', 'ci_exhaustion', 'exhaustFormInput(cargo,form,current)')),
|
|
@@ -269,6 +273,7 @@ const collectInput = {
|
|
|
269
273
|
SET('ci_validator', 'ci_val', "ci_spec.answer_validator || ''"),
|
|
270
274
|
SET('ci_reader_context', 'ci_reader', '({form:form,current:current,names:ci_names,questions:provides && Array.isArray(questions)?questions:[]})'),
|
|
271
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)'),
|
|
272
277
|
// a key the input routes to a handler of its own (route_keys: [{ prefix, flow }]): that flow takes the answer
|
|
273
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] || ''"),
|
|
274
279
|
WHEN('ci_route_key', 'ci_routed', FLOW('ci_route_key_flow', '{{ci_routed}}', 'call', { form: '{{form}}', input: '{{current}}' })),
|
|
@@ -296,7 +301,7 @@ const collectInput = {
|
|
|
296
301
|
'condition: ci_m.hits.length > 0': FLOW('ci_accept', 'collect-input-accept', 'call', { hits: '{{ci_m.hits}}' }),
|
|
297
302
|
"condition: ci_m.questions.length > 0": END('ci_question_handled'),
|
|
298
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}}' }),
|
|
299
|
-
"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'"),
|
|
300
305
|
default: SIDE('ci_miss', "(missFormInput(cargo,form,current),true)")
|
|
301
306
|
} },
|
|
302
307
|
],
|
|
@@ -416,7 +421,7 @@ const disambiguate = {
|
|
|
416
421
|
id: 'disambiguate-input', name: 'DisambiguateInput', version: '1.0.0', scope: 'isolated',
|
|
417
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.",
|
|
418
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' }],
|
|
419
|
-
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: [] }),
|
|
420
425
|
steps: [
|
|
421
426
|
WHEN('di_store_clear', 'Array.isArray(hits) && hits.length > 0', FLOW('di_store_hits', 'collect-input-accept', 'call', { hits: '{{hits}}' })),
|
|
422
427
|
SET('di_number', 'di_keys', `${KEYS_JS}(readings.length)`),
|
|
@@ -426,17 +431,19 @@ const disambiguate = {
|
|
|
426
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(', ') + '.'"),
|
|
427
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(', ') + '.'"),
|
|
428
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);})'),
|
|
429
435
|
SET('di_counter', 'di_input', "'__ambiguity:'+readings.map(function(r){return r.form+':'+r.name;}).sort().join('|')"),
|
|
430
436
|
SET('di_prompt', 'di_prompt', "formInputPrompt(cargo,di_form,di_input,{question:di_q,question_es:di_q_es},{},'en')"),
|
|
431
437
|
SET('di_prompt_es', 'di_prompt_es', "formInputPrompt(cargo,di_form,di_input,{question:di_q,question_es:di_q_es},{},'es')"),
|
|
432
438
|
SIDE('di_feedback_consumed', 'consumeFormFeedback(cargo,di_form,di_input)'),
|
|
433
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)'),
|
|
434
441
|
SET('di_picked', 'di_pick', "/^r\\d+$/.test(String(cargo.choice || '')) ? [readings[Number(String(cargo.choice).slice(1))]] : null"),
|
|
435
442
|
WHEN('di_understood', 'di_pick', SIDE('di_clear_misses', 'clearFormMisses(cargo,di_form,di_input)')),
|
|
436
443
|
SET('di_word_count', 'di_words', "String(cargo.choiceText || '').split(/\\s+/).filter(function(w){return /[a-záéíóúüñ]/i.test(w);}).length"),
|
|
437
|
-
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'")),
|
|
438
445
|
WHEN('di_miss', '!di_pick', SET('di_miss_count', 'di_misses', 'missFormInput(cargo,di_form,di_input)')),
|
|
439
|
-
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'")),
|
|
440
447
|
WHEN('di_retry', '!di_pick', FLOW('di_retry_question', 'disambiguate-input', 'replace', { readings: '{{readings}}', hits: [], remaining: '{{remaining}}' })),
|
|
441
448
|
WHEN('di_store_pick', 'di_pick', FLOW('di_store_pick_flow', 'collect-input-accept', 'call', { hits: '{{di_pick}}' })),
|
|
442
449
|
SET('di_remaining_first', 'di_next', 'Array.isArray(remaining) && remaining.length ? remaining[0] : []'),
|
|
@@ -449,7 +456,7 @@ const confirmForm = {
|
|
|
449
456
|
id: 'confirm-form', name: 'ConfirmForm', version: '1.0.0', scope: 'isolated',
|
|
450
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).",
|
|
451
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" }]),
|
|
452
|
-
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: '' }),
|
|
453
460
|
steps: [
|
|
454
461
|
SIDE('cf_reset', "(cargo.formConfirm = cargo.formConfirm || {}, cargo.formConfirm[form] = '', true)"),
|
|
455
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};}))`),
|
|
@@ -459,6 +466,7 @@ const confirmForm = {
|
|
|
459
466
|
SET('cf_prompt_es', 'cf_prompt_es', "formInputPrompt(cargo,form,'__confirm',{question:question,question_es:question_es},{},'es')"),
|
|
460
467
|
SIDE('cf_feedback_consumed', "consumeFormFeedback(cargo,form,'__confirm')"),
|
|
461
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)'),
|
|
462
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] || ''"),
|
|
463
471
|
WHEN('cf_interrupt', 'cf_event', FLOW('cf_event_flow', '{{cf_event}}', 'reboot', { form: '{{form}}', event: '{{cargo.choice}}', text: '{{cargo.choiceText}}' })),
|
|
464
472
|
SET('cf_build_chain', 'cf_chain', CHAIN_JS),
|
|
@@ -476,6 +484,7 @@ const confirmForm = {
|
|
|
476
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"),
|
|
477
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}}' })),
|
|
478
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')"),
|
|
479
488
|
{ id: 'cf_route', type: 'CASE', branches: {
|
|
480
489
|
'condition: cf_m.questions.length > 0': SET('cf_question_repeat', 'cf_result', "'changed'"),
|
|
481
490
|
"condition: cargo.choice === 'yes'": SET('cf_answered', 'cf_result', "'yes'"),
|
|
@@ -484,13 +493,13 @@ const confirmForm = {
|
|
|
484
493
|
'condition: cf_named': SET('cf_named_reset', 'cf_result', "(resetFormInputs(cargo, cf_named.form, [cf_named.name]), 'changed')"),
|
|
485
494
|
"condition: cargo.choice === 'no'": SET('cf_declined', 'cf_result', "'no'"),
|
|
486
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'"),
|
|
487
|
-
'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'"),
|
|
488
497
|
default: SET('cf_unclear', 'cf_result', "'unclear'")
|
|
489
498
|
} },
|
|
490
499
|
SET('cf_changed', 'cf_result', "cf_result ? cf_result : 'changed'"),
|
|
491
500
|
// an unclear short answer is asked again (the form runs again and reaches this question), at most twice
|
|
492
501
|
SET('cf_tries_count', 'cf_tries', "cf_result === 'unclear' ? missFormInput(cargo,form,'__confirm') : (clearFormMisses(cargo,form,'__confirm'),0)"),
|
|
493
|
-
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'")),
|
|
494
503
|
WHEN('cf_bind_revision', "cf_result === 'yes'", SIDE('cf_bind', 'confirmFormRevision(cargo, form)')),
|
|
495
504
|
SIDE('cf_store', "(cargo.formConfirm[form] = cf_result === 'unclear' || cf_result === 'clarification' ? 'changed' : cf_result, true)"),
|
|
496
505
|
],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../src/helpers/answer.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../src/helpers/answer.js"],"names":[],"mappings":"AA4LA;;;GAGG;AACH,wEAkBC;AAED,+HAA+H;AAC/H,oDAIC;AAcD,2GAA2G;AAC3G,uDAGC;AAOD;;;;;GAKG;AACH,+DAUC;AAKD;;;;GAIG;AACH,oDAEC;AAiBD;;;;;;GAMG;AACH,mDAAmF;AAEnF,kGAAkG;AAClG;;;EAmCC;AAUD;;;;;;;;;GASG;AACH,6EAyGC;;;;;;;;AA/ZD,qCAKiD"}
|
package/dist/helpers/answer.js
CHANGED
|
@@ -176,6 +176,9 @@ function textRequest(input, words, extra) {
|
|
|
176
176
|
* when `yesOk`, a YES word ("yes 1", "sí, 1"). Owner decision 2026-10-15: "Yes 1" at "tell me the product number" was
|
|
177
177
|
* missed. Anything else around the key ("no 1", "1 or 2", "I paid 1 already") is not that key.
|
|
178
178
|
*/
|
|
179
|
+
// Words that name the key itself when said right before it: "el número 4", "opción 2", "number 4", "the 4" (prod
|
|
180
|
+
// 2026-10-25 18:25: "Número 4" and "El número 4" at a numbered product list were not understood, twice).
|
|
181
|
+
const KEY_NAMES = new Set(['number', 'numero', 'option', 'opcion', 'item', 'choice', 'the', 'el', 'la'].map(normalize));
|
|
179
182
|
function keyWithOnlyCourtesy(b, key, yesOk) {
|
|
180
183
|
const tk = String(b).replace(/\./g, ' ').split(' ').filter(Boolean);
|
|
181
184
|
const at = tk.reduce((acc, t, i) => (t === key ? [...acc, i] : acc), []);
|
|
@@ -183,7 +186,10 @@ function keyWithOnlyCourtesy(b, key, yesOk) {
|
|
|
183
186
|
return false;
|
|
184
187
|
}
|
|
185
188
|
else {
|
|
186
|
-
|
|
189
|
+
let from = at[0];
|
|
190
|
+
while (from > 0 && at[0] - from < 2 && KEY_NAMES.has(tk[from - 1]))
|
|
191
|
+
from--;
|
|
192
|
+
const rest = tk.filter((t, i) => i < from || i > at[0]).join(' ');
|
|
187
193
|
return onlyAffirmative(rest, yesOk ? ANSWER_WORDS.yes : [], false);
|
|
188
194
|
}
|
|
189
195
|
}
|
|
@@ -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))
|
|
@@ -285,7 +287,7 @@ export function settleFormActivations(batch = {}, cargo = null) {
|
|
|
285
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) };
|
|
286
288
|
}
|
|
287
289
|
/** Explicit ownership: unrelated tasks and sibling inputs never share an instance by definition id. */
|
|
288
|
-
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 } = {}) {
|
|
289
291
|
const incomplete = validateFormIncompleteOutcome(incomplete_outcome);
|
|
290
292
|
validateFormExhaustion(on_exhausted);
|
|
291
293
|
cargo.forms ||= {};
|
|
@@ -301,6 +303,8 @@ export function openFormInstance(cargo, definition, specs, { parent = null, inpu
|
|
|
301
303
|
existing.incomplete_outcome = incomplete;
|
|
302
304
|
if (on_exhausted !== undefined)
|
|
303
305
|
existing.on_exhausted = on_exhausted;
|
|
306
|
+
if (must_complete !== undefined)
|
|
307
|
+
existing.must_complete = must_complete === true;
|
|
304
308
|
return resume;
|
|
305
309
|
}
|
|
306
310
|
if (parent && !cargo.forms[parent])
|
|
@@ -317,13 +321,15 @@ export function openFormInstance(cargo, definition, specs, { parent = null, inpu
|
|
|
317
321
|
cargo.forms[child].incomplete_outcome = incomplete;
|
|
318
322
|
if (on_exhausted !== undefined)
|
|
319
323
|
cargo.forms[child].on_exhausted = on_exhausted;
|
|
324
|
+
if (must_complete !== undefined)
|
|
325
|
+
cargo.forms[child].must_complete = must_complete === true;
|
|
320
326
|
return child;
|
|
321
327
|
}
|
|
322
328
|
}
|
|
323
329
|
cargo.formSequence = Number(cargo.formSequence || 0) + 1;
|
|
324
330
|
const id = `${definition}:${cargo.formSequence}`;
|
|
325
331
|
cargo.forms[id] = { id, definition, task: owner?.task || task || id, parent, result_input: input,
|
|
326
|
-
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,
|
|
327
333
|
providers: providers || owner?.providers || null, questions: questions?.length ? questions : owner?.questions || [], status: 'collecting' };
|
|
328
334
|
if (owner) {
|
|
329
335
|
if (context) {
|
|
@@ -708,6 +714,18 @@ export function pendingFormReadings(cargo, id) {
|
|
|
708
714
|
}
|
|
709
715
|
return orderFormReadings(cargo, out);
|
|
710
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
|
+
}
|
|
711
729
|
export function formWorkLimit(cargo, id) {
|
|
712
730
|
const ancestors = new Set();
|
|
713
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
|
};
|
package/dist/runtime-identity.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const JSFE_RUNTIME_FINGERPRINT = "sha256:
|
|
1
|
+
export const JSFE_RUNTIME_FINGERPRINT = "sha256:5dcc5703f78eb0d76716ae4ec5d702abefd508aac9a44b3cb67b62efc93e244d";
|
|
@@ -248,6 +248,8 @@ The loop skips accepted data and asks the first ready missing required field. Co
|
|
|
248
248
|
|
|
249
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.
|
|
250
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
|
+
|
|
251
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.
|
|
252
254
|
|
|
253
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,17 @@
|
|
|
1
|
+
## 1.10.5: a key named before it
|
|
2
|
+
|
|
3
|
+
At a numbered choice, a word naming the key said right before it picks the key: "el número 4", "opción 2", "number 4", "the
|
|
4
|
+
4" (`keyWithOnlyCourtesy`; prod 2026-10-25 18:25, "Número 4" and "El número 4" at a numbered product list were not
|
|
5
|
+
understood, twice, and the caller lost the flow). A number anywhere else in a sentence is still not a key.
|
|
6
|
+
|
|
7
|
+
## 1.10.4: forms that must complete
|
|
8
|
+
|
|
9
|
+
`must_complete:true` on a form or a field (a field inherits it from any enclosing form; `false` on a field opts out): an
|
|
10
|
+
unreadable answer, long or short, a confirmation miss and an unresolved ambiguity are asked again instead of yielding to
|
|
11
|
+
DISPATCH (reference, "Retry, yield and recovery"). Prod 2026-10-25: "el número postal el código postal" at the billing-ZIP
|
|
12
|
+
correction ended the payment. The form runaway guard now counts passes since the caller's last answer, so a caller who
|
|
13
|
+
needs several tries is not mistaken for a loop.
|
|
14
|
+
|
|
1
15
|
## 1.10.3: a field is ready before an unnamed answer fills it
|
|
2
16
|
|
|
3
17
|
A bare number never fills, out of order, a field whose `depends_on` prerequisites are not filled yet (`formInputChain`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsfe",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.5",
|
|
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",
|