jsfe 1.15.7 → 1.15.9

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.
@@ -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', 'must_complete']);
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', 'on_no', '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); };
@@ -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, must_complete }: {
1
+ export function formFlow({ id, name, version, description, inputs, after, extraVars, parameters, confirm, before_confirm, declined, stop, close_on_stop, on_no, 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;
@@ -12,6 +12,7 @@ export function formFlow({ id, name, version, description, inputs, after, extraV
12
12
  declined?: never[] | undefined;
13
13
  stop?: string | undefined;
14
14
  close_on_stop?: boolean | undefined;
15
+ on_no?: string | undefined;
15
16
  resume_on_parent_change?: boolean | undefined;
16
17
  before?: never[] | undefined;
17
18
  done?: boolean | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../../src/forms/flows.js"],"names":[],"mappings":"AAqIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4DC;AACD;;;GAGG;AACH;;;;;;EAGC;AAueD,wHAAwH;AACxH,wEAaC;AACD,4FAA4F;AAC5F;;;EAYC;AACD,sGAAsG;AACtG;;;EAOC;AA3qBM,6CAAuG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAo2B9G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAOG"}
1
+ {"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../../src/forms/flows.js"],"names":[],"mappings":"AAqIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6DC;AACD;;;GAGG;AACH;;;;;;EAGC;AAgfD,wHAAwH;AACxH,wEAaC;AACD,4FAA4F;AAC5F;;;EAYC;AACD,sGAAsG;AACtG;;;EAOC;AArrBM,6CAAuG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA82B9G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAOG"}
@@ -127,8 +127,10 @@ function validateAcceptanceDeclarations(id, inputs) {
127
127
  else { /* no invites, or a list of names */ }
128
128
  }
129
129
  }
130
- 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 }) {
130
+ export function formFlow({ id, name, version, description, inputs, after = [], extraVars = {}, parameters = [], confirm = null, before_confirm = [], declined = [], stop = '', close_on_stop = false, on_no = '', 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 }) {
131
131
  validateFormExhaustion(on_exhausted);
132
+ if (on_no !== '' && (typeof on_no !== 'string' || !/^[\w-]+$/.test(on_no)))
133
+ throw new Error(`FORM ${id}: on_no must be a flow id`);
132
134
  if (typeof must_complete !== 'boolean')
133
135
  throw new Error(`FORM ${id}: must_complete must be true or false`);
134
136
  validateAcceptanceDeclarations(id, inputs);
@@ -154,13 +156,13 @@ export function formFlow({ id, name, version, description, inputs, after = [], e
154
156
  ] : [];
155
157
  return {
156
158
  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 } : {}),
157
- 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 }),
159
+ variables: vars({ form_on_exhausted: on_exhausted, form_on_no: on_no, 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 }),
158
160
  steps: [
159
161
  ...contextualResponseRuntimeSteps(id, [], hasFormResponse(inputs) ? [CONTEXTUAL_RESPONSES_REQUIREMENT] : []),
160
162
  ...(hasFormResponse(inputs) ? [{ ...SET(`${id}_response_reader_required`, 'noop', "typeof validateFormResponse === 'function'"), requireTrue: true }] : []),
161
163
  ...proposalRuntimeSteps(id, [], hasInputProposals(inputs) ? [INPUT_PROPOSALS_REQUIREMENT] : []),
162
164
  SET(`${id}_resume_kind`, 'form_internal_resume', '!!form_instance'),
163
- 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 })`),
165
+ 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, on_no: form_on_no, must_complete: form_must_complete })`),
164
166
  SIDE(`${id}_task`, `(!parent_form ? (cargo.formTasks = cargo.formTasks || {}, cargo.formTasks[${JSON.stringify(id)}] = form_instance) : true, true)`),
165
167
  SIDE(`${id}_provider_reset`, '(delete cargo.forms[form_instance].providerRequests, delete cargo.forms[form_instance].activeGates, delete cargo.forms[form_instance].providerGateActive, true)'),
166
168
  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)'),
@@ -250,7 +252,7 @@ const collectInput = {
250
252
  id: 'collect-input', name: 'CollectInput', version: '1.0.0', scope: 'isolated',
251
253
  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).",
252
254
  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 })),
253
- variables: vars({ ci_partial: '', ci_reply_reader: '', 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: '' }),
255
+ variables: vars({ ci_partial: '', ci_no_owner: '', ci_no_flow: '', ci_no_up: false, ci_reply_reader: '', 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: '' }),
254
256
  steps: [
255
257
  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; })()"),
256
258
  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]"),
@@ -299,6 +301,15 @@ const collectInput = {
299
301
  // short of the input's minimum the next question is "Go on." Anything that is not digits drops the partial.
300
302
  SET('ci_partial_join', 'ci_partial', "(function () { var state = cargo.forms[form]; if (!ci_spec || ci_spec.accumulate !== true || !ci_spec.digits) return ''; var said = String(cargo.choiceText || '').trim(); var data = state.data = state.data || {}; var key = '__partial_' + current; if (!/^[0-9][0-9\\s.,-]*$/.test(said)) { delete data[key]; return ''; } var joined = String(data[key] || '') + said.replace(/[^0-9]/g, ''); var starts = ci_spec.accumulate_first ? new RegExp(ci_spec.accumulate_first) : null; if (!data[key] && starts && !starts.test(joined)) return ''; if (joined.length < Number(ci_spec.digits.min || 0)) { data[key] = joined; var fb = state.feedback || {}; fb[current] = 'partial'; state.feedback = fb; return 'hold'; } delete data[key]; cargo.choiceText = joined; cargo.choice = joined; return 'joined'; })()"),
301
303
  WHEN('ci_partial_hold', "ci_partial === 'hold'", END('ci_partial_wait')),
304
+ // A whole NO at a question that does not read it, in a form (or a sub-form of one) that declares `on_no`: that
305
+ // form's worker answers it (a payment starts its selection again), instead of any field or the reply ranker; the
306
+ // owning form then continues, a sub-form's owner included.
307
+ SET('ci_no_owner', 'ci_no_owner', "formNoOwner(cargo, form, ci_spec, cargo.choiceText)"),
308
+ SET('ci_no_flow', 'ci_no_flow', "ci_no_owner ? cargo.forms[ci_no_owner].on_no : ''"),
309
+ SET('ci_no_up', 'ci_no_up', "!!ci_no_owner && ci_no_owner !== form"), // before the call: a shared-scope worker may set `form`
310
+ WHEN('ci_no_run', 'ci_no_flow', FLOW('ci_no_answer', '{{ci_no_flow}}', 'call', { form: '{{ci_no_owner}}' })),
311
+ WHEN('ci_no_owner_resume', 'ci_no_flow && ci_no_up', FLOW('ci_no_resume_owner', 'resume-form', 'reboot', { form: '{{ci_no_owner}}' })),
312
+ WHEN('ci_no_end', 'ci_no_flow', END('ci_no_done')),
302
313
  SET('ci_build_chain', 'ci_chain', CHAIN_JS),
303
314
  SET('ci_match', 'ci_m', "readFormAnswer(cargo.choiceText, ci_chain, Array.isArray(ci_spec.any_of) ? { form: form, names: ci_names } : { form: form, name: current }, cargo.choice, provides && Array.isArray(questions) ? questions : [])"),
304
315
  SIDE('ci_acceptance_trace', "(cargo.formAcceptance = formAcceptanceTrace(cargo.formAcceptance, ci_m), true)"),
@@ -407,9 +418,9 @@ const rankedChoices = (id, from) => SET(id, 'ranked', `(${from}).concat(${ESCAPE
407
418
  const RANK_LABEL_JS = "function(c){var said=(Array.isArray(c.phrases) ? c.phrases : []).map(String).filter(function(p){return /[a-zà-ÿ]/i.test(p) && p!==String(c.key);}).slice(0,6);return String(c.label || (said.length ? c.key+' ('+said.join(', ')+')' : c.key));}";
408
419
  // The choices of this form's OTHER inputs that the answer could also set (the word reader reads them all: 'LO VENCIDO' at
409
420
  // the pay confirmation): the ranker is offered every choice the reader would accept (owner 2026-10-28). A confirmation
410
- // input is answered only when asked, and a key already offered is not offered twice. Each carries its input, so a pick is
411
- // read as that input's answer.
412
- const otherChoicesJS = (asked, taken) => `(function(){var seen=(${taken}).map(function(c){return String(c.key);}).concat(['yes','no']);return (chain || []).filter(function(i){return i.form===form && i.name!==${asked} && !i.confirmation && i.profile && i.profile.kind==='choice' && Array.isArray(i.profile.choices);}).reduce(function(all,i){return all.concat(i.profile.choices.filter(function(c){var k=String(c.key);if(seen.indexOf(k)>=0)return false;seen.push(k);return true;}).map(function(c){return {key:String(c.key),label:(${RANK_LABEL_JS})(c),owner:{form:i.form,name:i.name}};}));},[]);})()`;
421
+ // input is answered only when asked, a current_only choice only at its own question (as the reader reads it), and a key
422
+ // already offered is not offered twice. Each carries its input, so a pick is read as that input's answer.
423
+ const otherChoicesJS = (asked, taken) => `(function(){var seen=(${taken}).map(function(c){return String(c.key);}).concat(['yes','no']);return (chain || []).filter(function(i){return i.form===form && i.name!==${asked} && !i.confirmation && i.profile && i.profile.kind==='choice' && Array.isArray(i.profile.choices);}).reduce(function(all,i){return all.concat(i.profile.choices.filter(function(c){var k=String(c.key);if(c.current_only===true||seen.indexOf(k)>=0)return false;seen.push(k);return true;}).map(function(c){return {key:String(c.key),label:(${RANK_LABEL_JS})(c),owner:{form:i.form,name:i.name}};}));},[]);})()`;
413
424
  const escapeRoutes = (id) => [WHEN(`${id}_agent`, "reply && reply.key === '__agent'", FLOW(`${id}_agent_flow`, 'live-agent-requested', 'reboot'))];
414
425
  const rankedDispatch = (id, reason) => ({ ...yieldDispatch(id, 'form', `'${reason}'`), onResponse: 'continue', responseContext: 'Current question: {{asked}}', responseChoices: 'ranked', responseVariable: 'reply' });
415
426
  const readFormReply = {
@@ -502,8 +513,8 @@ const acceptedEffects = {
502
513
  WHEN('ie_none', '!ca_h', END('ie_end')),
503
514
  SET('ie_rest', 'ca_rest', 'readings.slice(1)'),
504
515
  WHEN('ie_reject', "ca_h.outcome === 'rejected'", SIDE('ie_attempt', "(cargo.forms[ca_h.form].attempts[ca_h.asked || ca_h.name] = Number(cargo.forms[ca_h.form].attempts[ca_h.asked || ca_h.name] || 0) + 1, cargo.forms[ca_h.form].feedback=cargo.forms[ca_h.form].feedback || {}, cargo.forms[ca_h.form].feedback[ca_h.asked || ca_h.name]='invalid', true)")),
505
- SET('ie_say_en', 'ca_ack', "ca_h.outcome === 'rejected' && ca_h.quiet ? '' : ca_h.outcome === 'rejected' ? (ca_h.message || ('That ' + (ca_h.label || ca_h.name) + \" doesn't look right.\")) : ca_h.silent === true || ca_h.fromPending ? '' : ca_h.outcome === 'known' && !ca_h.current ? ('Thank you, I already have your ' + ca_h.label + '.') : ca_h.outcome === 'changed' ? ('Okay, I noted your ' + ca_h.label + '.') : (ca_h.outcome === 'taken' || ca_h.outcome === 'pending') && ca_h.origin !== 'activation' ? ('Got it, your ' + ca_h.label + '.') : ''"),
506
- SET('ie_say_es', 'ca_ack_es', "ca_h.outcome === 'rejected' && ca_h.quiet ? '' : ca_h.outcome === 'rejected' ? (ca_h.message_es || ca_h.message || ('Ese ' + (ca_h.label_es || ca_h.label || ca_h.name) + ' no parece correcto.')) : ca_h.silent === true || ca_h.fromPending ? '' : ca_h.outcome === 'known' && !ca_h.current ? ('Gracias, ya tengo su ' + ca_h.label_es + '.') : ca_h.outcome === 'changed' ? ('Listo, anoté su ' + ca_h.label_es + '.') : (ca_h.outcome === 'taken' || ca_h.outcome === 'pending') && ca_h.origin !== 'activation' ? ('Entendido, su ' + ca_h.label_es + '.') : ''"),
516
+ SET('ie_say_en', 'ca_ack', "ca_h.outcome === 'rejected' && ca_h.quiet ? '' : ca_h.outcome === 'rejected' ? (ca_h.message || ('That ' + (ca_h.label || ca_h.name) + \" doesn't look right.\")) : ca_h.silent === true || ca_h.fromPending ? '' : ca_h.outcome === 'known' && !ca_h.current ? ('Thank you, I already have your ' + ca_h.label + '.') : ca_h.outcome === 'changed' && ca_h.origin !== 'activation' ? ('Okay, I noted your ' + ca_h.label + '.') : (ca_h.outcome === 'taken' || ca_h.outcome === 'pending') && ca_h.origin !== 'activation' ? ('Got it, your ' + ca_h.label + '.') : ''"),
517
+ SET('ie_say_es', 'ca_ack_es', "ca_h.outcome === 'rejected' && ca_h.quiet ? '' : ca_h.outcome === 'rejected' ? (ca_h.message_es || ca_h.message || ('Ese ' + (ca_h.label_es || ca_h.label || ca_h.name) + ' no parece correcto.')) : ca_h.silent === true || ca_h.fromPending ? '' : ca_h.outcome === 'known' && !ca_h.current ? ('Gracias, ya tengo su ' + ca_h.label_es + '.') : ca_h.outcome === 'changed' && ca_h.origin !== 'activation' ? ('Listo, anoté su ' + ca_h.label_es + '.') : (ca_h.outcome === 'taken' || ca_h.outcome === 'pending') && ca_h.origin !== 'activation' ? ('Entendido, su ' + ca_h.label_es + '.') : ''"),
507
518
  WHEN('ie_say', 'ca_ack', SAY('ie_ack', '{{ca_ack}}', '{{ca_ack_es}}')),
508
519
  WHEN('ie_callback', "ca_h.after && ca_h.outcome !== 'known' && ca_h.outcome !== 'rejected' && ca_h.outcome !== 'pending'", FLOW('ie_after', '{{ca_h.after}}', 'call', { form: '{{ca_h.form}}', input: '{{ca_h.name}}', value: '{{ca_h.value}}' })),
509
520
  WHEN('ie_more', 'ca_rest.length > 0', FLOW('ie_next', 'input-accepted-effects', 'replace', { readings: '{{ca_rest}}' })),
@@ -521,8 +532,8 @@ const disambiguate = {
521
532
  SET('di_build', 'di_choices', "readings.map(function (r, i) { var own = String(r.label + ' ' + r.label_es).toLowerCase().split(/\\s+/).filter(function (w) { return w.length > 3 && !readings.some(function (o, j) { return j !== i && String(o.label + ' ' + o.label_es).toLowerCase().split(/\\s+/).indexOf(w) > -1; }); }); return { key: 'r' + i, phrases: [di_keys[i], r.label, r.label_es].concat(own) }; })"),
522
533
  SET('di_said', 'di_value', "readings[0].pii ? '' : String(readings[0].value)"),
523
534
  // Options of ONE input (a phrase two choices share) are asked by name: "Do you mean A or B?". The value is a choice key, never words to say.
524
- 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(', ') + '.'"),
525
- 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(', ') + '.'"),
535
+ 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 ' : (i ? String(cargo.verb || 'type') : String(cargo.verb || 'type').charAt(0).toUpperCase() + String(cargo.verb || 'type').slice(1)) + ' ') + r.label.toUpperCase(); }).join(', ') + '.'"),
536
+ 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 ' : (i ? String(cargo.verb_es || 'escriba') : String(cargo.verb_es || 'escriba').charAt(0).toUpperCase() + String(cargo.verb_es || 'escriba').slice(1)) + ' ') + r.label_es.toUpperCase(); }).join(', ') + '.'"),
526
537
  SET('di_owner', 'di_form', 'readings[0].form'),
527
538
  SET('di_must_complete', 'di_must', 'readings.some(function(r){return formMustComplete(cargo,r.form,r.name);})'),
528
539
  SET('di_counter', 'di_input', "'__ambiguity:'+readings.map(function(r){return r.form+':'+r.name;}).sort().join('|')"),
@@ -1 +1 @@
1
- {"version":3,"file":"identity-flows.d.ts","sourceRoot":"","sources":["../../src/forms/identity-flows.js"],"names":[],"mappings":"AA2BA,+GAA+G;AAC/G,oFAOC;AAQD,wFAAwF;AACxF,iDAGC;AACD,6CAMC;AACD,qHAQC;AAMD,4EAKC;AAoFD,4DAKC;AAgHD,8EAGC;AA4BD,4FAA4F;AAC5F;;;;EAOC;AAQD,0BAAmI"}
1
+ {"version":3,"file":"identity-flows.d.ts","sourceRoot":"","sources":["../../src/forms/identity-flows.js"],"names":[],"mappings":"AA2BA,+GAA+G;AAC/G,oFAOC;AAQD,wFAAwF;AACxF,iDAGC;AACD,6CAMC;AACD,qHAQC;AAMD,4EAKC;AAoFD,4DAKC;AAmHD,8EAGC;AA4BD,4FAA4F;AAC5F;;;;EAOC;AAQD,0BAAmI"}
@@ -280,8 +280,11 @@ for (const flow of [auth, validateCode]) {
280
280
  flow.steps.unshift(...otpMenuMessages.map(([name, value]) => set(name, name, value.includes('{{') ? value : JSON.stringify(value))));
281
281
  }
282
282
  const retryParams = [param('error_message', 'Failure sentence'), param('error_message_es', 'Spanish failure sentence'), param('retry_flow', 'Retry destination'), param('cancel_flow', 'Exit destination'), param('capture_patterns', 'Declared contact capture readers', 'array'), param('retry_call_type', 'reboot or replace'), param('allow_code_entry', 'Accept the active six-digit challenge', 'boolean'), param('question', 'Retry question'), param('question_es', 'Spanish retry question'), param('caller_outcome', 'Unresolved outcome'), param('caller_reason', 'Unresolved reason'), param('parent_form', 'Owning task FORM instance'), param('contact_options', 'Contact field protocol declaration', 'object')];
283
- const retry = worker('generic-retry-with-options', 'GenericRetryWithOptions', 'Collect a retry, alternate contact, code or exit through the shared FORM adapter.', { scope: 'isolated', parameters: retryParams, variables: vars({ error_message: '', error_message_es: '', retry_flow: 'authenticate-user', cancel_flow: 'contact-support', capture_patterns: [], retry_call_type: 'reboot', allow_code_entry: false, question: '', question_es: '', caller_outcome: '', caller_reason: '', parent_form: '', contact_options: null, user_choice: '', smart_capture_result: null, normalized_otp_code: '', retry_question: '', retry_question_es: '', retry_yes_words: ['phone', 'email', 'cell', 'mobile', 'teléfono', 'telefono', 'celular', 'móvil', 'movil', 'correo electrónico', 'correo', 'try again', 'try it again', 'retry', 'intentar', 'intentarlo', 'intentar de nuevo', 'intentar nuevamente', 'intentarlo de nuevo', 'intentarlo otra vez', 'de nuevo', 'nuevamente', 'otra vez', 'reintentar', 'volver a intentar', 'volver a intentarlo'], retry_profiles: [{ name: 'phone', label: 'phone number', label_es: 'número de teléfono', profile: { kind: 'phone' } }, { name: 'email_address', label: 'email address', label_es: 'correo electrónico', profile: { kind: 'email' } }, ...OTP_PROFILES] }), contract: { role: 'collector', summary: 'Common bounded service retry menu' }, steps: [
283
+ const retry = worker('generic-retry-with-options', 'GenericRetryWithOptions', 'Collect a retry, alternate contact, code or exit through the shared FORM adapter.', { scope: 'isolated', parameters: retryParams, variables: vars({ error_message: '', error_message_es: '', retry_flow: 'authenticate-user', cancel_flow: 'contact-support', capture_patterns: [], retry_call_type: 'reboot', allow_code_entry: false, question: '', question_es: '', caller_outcome: '', caller_reason: '', parent_form: '', contact_options: null, user_choice: '', smart_capture_result: null, normalized_otp_code: '', retry_question: '', retry_question_es: '', retry_yes_words: ['phone', 'email', 'cell', 'mobile', 'teléfono', 'telefono', 'celular', 'móvil', 'movil', 'correo electrónico', 'correo', 'try again', 'try it again', 'retry', 'intentar', 'intentarlo', 'intentar de nuevo', 'intentar nuevamente', 'intentarlo de nuevo', 'intentarlo otra vez', 'de nuevo', 'nuevamente', 'otra vez', 'reintentar', 'volver a intentar', 'volver a intentarlo'], retry_otp_profiles: OTP_PROFILES, retry_profiles_base: [{ name: 'phone', label: 'phone number', label_es: 'número de teléfono', profile: { kind: 'phone' } }, { name: 'email_address', label: 'email address', label_es: 'correo electrónico', profile: { kind: 'email' } }], retry_profiles: [] }), contract: { role: 'collector', summary: 'Common bounded service retry menu' }, steps: [
284
284
  set('retry_parent', 'parent_form', "parent_form || cargo.authOptions && cargo.authOptions.parent_form || cargo.contactOptions && cargo.contactOptions.parent_form || ''"),
285
+ // The menu answers what its capture declares: an account number in the tenant's pattern (get-cell-or-email's
286
+ // account_pattern, carried in cargo.authCapture) is the menu's own answer, never the parent's, so it reaches the lookup.
287
+ set('retry_profiles', 'retry_profiles', "retry_profiles_base.concat((Array.isArray(capture_patterns) ? capture_patterns : []).filter(function (c) {return c && c.variable === 'acct_number' && c.regex;}).slice(0,1).map(function (c) {return {name:'account',label:'account number',label_es:'número de cuenta',profile:{kind:'identifier',pattern:c.regex}};}),retry_otp_profiles)"),
285
288
  set('retry_question', 'retry_question', "error_message+' '+(question || 'Would you like to try again?')+' To retry '+(cargo.voice?'Press 1 or ':'')+cargo.verb+' YES. '+(callerTextNumber(cargo)?'To switch to text press 9 or '+cargo.verb+' TEXT. ':'')+'To abort '+(cargo.voice?'press the asterisk key or ':'')+cargo.verb+' EXIT.'"),
286
289
  set('retry_question_es', 'retry_question_es', "error_message_es+' '+(question_es || '¿Le gustaría intentar nuevamente?')+' Para reintentar '+(cargo.voice?'Presione 1 o ':'')+cargo.verb_es+' SÍ. '+(callerTextNumber(cargo)?'Para cambiar a texto presione 9 o '+cargo.verb_es+' TEXTO. ':'')+'Para salir '+(cargo.voice?'presione la tecla de asterisco o ':'')+cargo.verb_es+' SALIR.'"),
287
290
  call('retry_collect', 'collect-service-answer', { profiles: '{{retry_profiles}}', question: '{{retry_question}}', question_es: '{{retry_question_es}}', keypad: { min: 1, max: 1 }, parent_form: '{{parent_form}}' }),
@@ -3,6 +3,8 @@
3
3
  * YES, AGENT and TEXT never match a negated answer ("not sure", "I don't want an agent"); 0 is AGENT, * is EXIT.
4
4
  */
5
5
  export function isAnswer(input: any, kind: any, extra?: any[]): boolean;
6
+ /** Whether the WHOLE answer is a NO ("No.", "no gracias", "nope, thanks"), never a sentence that starts with one. */
7
+ export function isWholeNo(input: any): boolean;
6
8
  /**
7
9
  * Whether the WHOLE answer is an exit word ("exit", "Cancelar.", "salir por favor", "*"), never a sentence that contains
8
10
  * one: "Cancelar el pedido" asks for something; it does not withdraw (prod 2026-10-26, at a verification-code prompt).
@@ -1 +1 @@
1
- {"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../src/helpers/answer.js"],"names":[],"mappings":"AAsNA;;;GAGG;AACH,wEAkBC;AAED;;;;GAIG;AACH,mDAIC;AAED,+HAA+H;AAC/H,oDAIC;AAcD,2GAA2G;AAC3G,uDAGC;AAOD;;;;;GAKG;AACH,+DAUC;AAKD;;;;GAIG;AACH,oDAEC;AAqBD;;;;;;GAMG;AACH,mDAAmF;AAEnF,kGAAkG;AAClG;;;EAoCC;AAUD;;;;;;;;;GASG;AACH,6EA4GC;;;;;;;;AAxcD,qCAKwE"}
1
+ {"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../src/helpers/answer.js"],"names":[],"mappings":"AAsNA;;;GAGG;AACH,wEAkBC;AAED,qHAAqH;AACrH,+CAGC;AAED;;;;GAIG;AACH,mDAIC;AAED,+HAA+H;AAC/H,oDAIC;AAcD,2GAA2G;AAC3G,uDAGC;AAOD;;;;;GAKG;AACH,+DAUC;AAKD;;;;GAIG;AACH,oDAEC;AAqBD;;;;;;GAMG;AACH,mDAAmF;AAEnF,kGAAkG;AAClG;;;EAoCC;AAUD;;;;;;;;;GASG;AACH,6EA4GC;;;;;;;;AA9cD,qCAKwE"}
@@ -250,6 +250,11 @@ export function isAnswer(input, kind, extra = []) {
250
250
  return matchesChoice(b, words);
251
251
  }
252
252
  }
253
+ /** Whether the WHOLE answer is a NO ("No.", "no gracias", "nope, thanks"), never a sentence that starts with one. */
254
+ export function isWholeNo(input) {
255
+ const b = normalize(input);
256
+ return !!b && onlyAffirmative(b, ANSWER_WORDS.no);
257
+ }
253
258
  /**
254
259
  * Whether the WHOLE answer is an exit word ("exit", "Cancelar.", "salir por favor", "*"), never a sentence that contains
255
260
  * one: "Cancelar el pedido" asks for something; it does not withdraw (prod 2026-10-26, at a verification-code prompt).
@@ -54,4 +54,7 @@ export function cancelFormTask(cargo: any, id: any): {
54
54
  flow: any;
55
55
  };
56
56
  };
57
+ /** The form that answers a whole NO said at this question: the nearest form up the chain (this one, or a sub-form's owner)
58
+ * that declares `on_no`, when the question is not a yes/no or confirmation and does not read the NO itself. '' if none. */
59
+ export function formNoOwner(cargo: any, id: any, spec: any, answer: any): any;
57
60
  //# sourceMappingURL=form-conversation.d.ts.map
@@ -1 +1 @@
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;;;GAGG;AACH,+GAQC;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":"AAKA,0EAGC;AACD,oEAGC;AACD;;qGAEqG;AACrG,2EAWC;AACD;;;GAGG;AACH,+GAQC;AACD,8EAAwI;AAExI,yGAAyG;AACzG;;;;;;;;;;;;;;;;;;;;EAUC;AAED,sGAAsG;AACtG;;;;;;;;;;;;;;;;;;;;EAeC;AAED;2HAC2H;AAC3H,8EAOC"}
@@ -1,5 +1,7 @@
1
1
  /** Shared FORM retry, contextual yield and recovery policy; all state is conversation-owned. */
2
2
  import { formIncompleteOutcome, closeFormInstance } from './form-state.js';
3
+ import { isWholeNo } from './answer.js';
4
+ import { readInputProfile } from './match-inputs.js';
3
5
  export function clearFormMisses(cargo, id, input) {
4
6
  const state = cargo.forms?.[id];
5
7
  if (!state)
@@ -110,3 +112,19 @@ export function cancelFormTask(cargo, id) {
110
112
  closeFormInstance(cargo, owner, false);
111
113
  return { outcome: 'resolved', reason: 'request_cancelled', recovery: { version: 1, mode: 'none', reason: 'request_cancelled' } };
112
114
  }
115
+ /** The form that answers a whole NO said at this question: the nearest form up the chain (this one, or a sub-form's owner)
116
+ * that declares `on_no`, when the question is not a yes/no or confirmation and does not read the NO itself. '' if none. */
117
+ export function formNoOwner(cargo, id, spec, answer) {
118
+ const raw = String(answer ?? '').replace(/\\(['"\\])/g, '$1').trim();
119
+ if (!raw || !isWholeNo(raw) || !spec || spec.confirmation || spec.proposal)
120
+ return '';
121
+ if ([spec, ...(Array.isArray(spec.any_of) ? spec.any_of : [])].some(s => s && s.profile && readInputProfile(s.profile, raw, true)))
122
+ return ''; // the question offers NO itself
123
+ const seen = new Set();
124
+ for (let f = id; f && cargo.forms?.[f] && !seen.has(f); f = cargo.forms[f].parent) {
125
+ seen.add(f);
126
+ if (cargo.forms[f].on_no)
127
+ return f;
128
+ }
129
+ return '';
130
+ }
@@ -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, must_complete }?: {
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, on_no, 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
+ on_no?: undefined;
32
33
  must_complete?: undefined;
33
34
  }): any;
34
35
  /** An incomplete optional follow-up may preserve an explicitly completed task outcome. */
@@ -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,oEA8BC;AA0CD,2FAEC;AACD,8FAcC;AACD,2DAOC;AACD,uEA6CC;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;AAnpBM,8CAAmE"}
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;AA0CD,2FAEC;AACD,8FAcC;AACD,2DAOC;AACD,uEA6CC;AAED,kGAAkG;AAClG,2GAOC;AACD,0HAA0H;AAC1H,uEAwBC;AACD,8HAA8H;AAC9H;;;;;EAWC;AACD,uGAAuG;AACvG;;;;;;;;;;;;;QAoCC;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;AArpBM,8CAAmE"}
@@ -293,7 +293,7 @@ export function settleFormActivations(batch = {}, cargo = null) {
293
293
  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) };
294
294
  }
295
295
  /** Explicit ownership: unrelated tasks and sibling inputs never share an instance by definition id. */
296
- 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 } = {}) {
296
+ 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, on_no = undefined, must_complete = undefined } = {}) {
297
297
  const incomplete = validateFormIncompleteOutcome(incomplete_outcome);
298
298
  validateFormExhaustion(on_exhausted);
299
299
  cargo.forms ||= {};
@@ -309,6 +309,8 @@ export function openFormInstance(cargo, definition, specs, { parent = null, inpu
309
309
  existing.incomplete_outcome = incomplete;
310
310
  if (on_exhausted !== undefined)
311
311
  existing.on_exhausted = on_exhausted;
312
+ if (on_no !== undefined)
313
+ existing.on_no = on_no || '';
312
314
  if (must_complete !== undefined)
313
315
  existing.must_complete = must_complete === true;
314
316
  return resume;
@@ -327,6 +329,8 @@ export function openFormInstance(cargo, definition, specs, { parent = null, inpu
327
329
  cargo.forms[child].incomplete_outcome = incomplete;
328
330
  if (on_exhausted !== undefined)
329
331
  cargo.forms[child].on_exhausted = on_exhausted;
332
+ if (on_no !== undefined)
333
+ cargo.forms[child].on_no = on_no || '';
330
334
  if (must_complete !== undefined)
331
335
  cargo.forms[child].must_complete = must_complete === true;
332
336
  return child;
@@ -335,7 +339,7 @@ export function openFormInstance(cargo, definition, specs, { parent = null, inpu
335
339
  cargo.formSequence = Number(cargo.formSequence || 0) + 1;
336
340
  const id = `${definition}:${cargo.formSequence}`;
337
341
  cargo.forms[id] = { id, definition, task: owner?.task || task || id, parent, result_input: input,
338
- 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,
342
+ on_exhausted: on_exhausted || '', on_no: on_no || '', 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,
339
343
  providers: providers || owner?.providers || null, questions: questions?.length ? questions : owner?.questions || [], status: 'collecting' };
340
344
  if (owner) {
341
345
  if (context) {
@@ -18,6 +18,7 @@ export namespace STANDARD_FUNCTIONS {
18
18
  export { validateFormProposalDescriptor };
19
19
  export { formProposalDescriptor };
20
20
  export { clearFormMisses };
21
+ export { formNoOwner };
21
22
  export { missFormInput };
22
23
  export { formMustComplete };
23
24
  export { formInputPrompt };
@@ -136,6 +137,7 @@ import { validateFormProposalPolicy } from './proposal-contract.js';
136
137
  import { validateFormProposalDescriptor } from './proposal-contract.js';
137
138
  import { formProposalDescriptor } from './proposal-contract.js';
138
139
  import { clearFormMisses } from './form-conversation.js';
140
+ import { formNoOwner } from './form-conversation.js';
139
141
  import { missFormInput } from './form-conversation.js';
140
142
  import { formMustComplete } from './form-conversation.js';
141
143
  import { formInputPrompt } from './form-conversation.js';
@@ -240,5 +242,5 @@ import { validatePhone } from './validators.js';
240
242
  import { validateEmail } from './validators.js';
241
243
  import { urlToSpeech } from './url-speech.js';
242
244
  import { textWithUrlToSpeech } from './url-speech.js';
243
- 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, isExitRequest, 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 };
245
+ export { continuationSafetyProtocol, cancelFormTask, contextualResponseProtocol, validateFormResponse, hasFormResponse, formProposalPolicy, stageFormProposal, nextFormProposal, formProposalReadings, bindFormProposal, decideFormProposal, commitFormProposals, settleFormProposalValidation, formProposalProtocol, validateFormProposalPolicy, validateFormProposalDescriptor, formProposalDescriptor, clearFormMisses, formNoOwner, missFormInput, formMustComplete, formInputPrompt, consumeFormFeedback, formConversationOutcome, readMoneyAmount, isMoneyAmount, moneyIntentAllows, isPastPaymentStatement, isPartialMoneyAnswer, validateMoneyOptions, takeChoiceOffer, matchesChoice, ANSWER_WORDS, CONFIRM_WORDS, isAnswer, isAgentRequest, isExitRequest, 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 };
244
246
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
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;6BAEiH,aAAa;8BAAb,aAAa;yBAAb,aAAa;+BAAb,aAAa;8BAAb,aAAa;+BAAb,aAAa;kCAAb,aAAa;+BAAb,aAAa;+BAAb,aAAa;2BAAb,aAAa;0BACxB,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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/helpers/index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2CACyC,iCAAiC;+BAI0E,wBAAwB;2CAH9F,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;gCACQ,wBAAwB;4BAAxB,wBAAwB;8BAAxB,wBAAwB;iCAAxB,wBAAwB;gCAAxB,wBAAwB;oCAAxB,wBAAwB;wCAAxB,wBAAwB;gCAmB7C,YAAY;8BAAZ,YAAY;kCAAZ,YAAY;uCAAZ,YAAY;qCAAZ,YAAY;qCAAZ,YAAY;gCAlB3G,mBAAmB;8BAiBrB,qBAAqB;6BAEiH,aAAa;8BAAb,aAAa;yBAAb,aAAa;+BAAb,aAAa;8BAAb,aAAa;+BAAb,aAAa;kCAAb,aAAa;+BAAb,aAAa;+BAAb,aAAa;2BAAb,aAAa;0BACxB,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"}
@@ -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, formMustComplete, formInputPrompt, consumeFormFeedback, formConversationOutcome } from './form-conversation.js';
6
+ import { cancelFormTask, clearFormMisses, formNoOwner, 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
  /**
@@ -35,12 +35,12 @@ import { resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, fo
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, formMustComplete, formInputPrompt, consumeFormFeedback, formConversationOutcome, readMoneyAmount, isMoneyAmount, moneyIntentAllows, isPastPaymentStatement, isPartialMoneyAnswer, validateMoneyOptions, takeChoiceOffer, matchesChoice, ANSWER_WORDS, CONFIRM_WORDS, isAnswer, isAgentRequest, isExitRequest, 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, };
38
+ export { continuationSafetyProtocol, cancelFormTask, contextualResponseProtocol, validateFormResponse, hasFormResponse, formProposalPolicy, stageFormProposal, nextFormProposal, formProposalReadings, bindFormProposal, decideFormProposal, commitFormProposals, settleFormProposalValidation, formProposalProtocol, validateFormProposalPolicy, validateFormProposalDescriptor, formProposalDescriptor, clearFormMisses, formNoOwner, missFormInput, formMustComplete, formInputPrompt, consumeFormFeedback, formConversationOutcome, readMoneyAmount, isMoneyAmount, moneyIntentAllows, isPastPaymentStatement, isPartialMoneyAnswer, validateMoneyOptions, takeChoiceOffer, matchesChoice, ANSWER_WORDS, CONFIRM_WORDS, isAnswer, isAgentRequest, isExitRequest, 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, formMustComplete, formInputPrompt, consumeFormFeedback, formConversationOutcome,
43
+ clearFormMisses, formNoOwner, missFormInput, formMustComplete, formInputPrompt, consumeFormFeedback, formConversationOutcome,
44
44
  readMoneyAmount, isMoneyAmount, moneyIntentAllows, isPastPaymentStatement, isPartialMoneyAnswer, validateMoneyOptions, takeChoiceOffer, matchesChoice, isAnswer, isAgentRequest, isExitRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice,
45
45
  readEmail, readPhone, readInternationalPhone, readIdentifier, last4, maskEmail, callerTextNumber, textDestination, callerIsInternational,
46
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,
@@ -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;;;;;;;;;;;;;;;;;;;;;;EAoMC"}
1
+ {"version":3,"file":"match-inputs.d.ts","sourceRoot":"","sources":["../../src/helpers/match-inputs.js"],"names":[],"mappings":"AAiBA;;;;GAIG;AACH,sEAQC;AAiFD;;;GAGG;AACH,sFA0GC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;mHACmH;AACnH;;;;;;;;;;;;;;;;;;;;;EAeC;AACD;;;;;;;;;;;;;;;;;;;;;;EAoOC"}
@@ -37,6 +37,17 @@ function unescape(answer) {
37
37
  function digitRuns(raw) {
38
38
  return (raw.match(/\d(?:[\s.-]?\d)*/g) || []).map((r) => r.replace(/\D/g, ''));
39
39
  }
40
+ const DECIMAL_AMOUNT = /(?:[$€£]\s?)?\d+[.,]\d{2}(?!\d)/g; // a written decimal amount, with its currency mark
41
+ /** Where the answer says amount n: a written decimal by itself ("51 72.45" is not one spoken run), else its number spans. */
42
+ function amountSpans(raw, n) {
43
+ const decimals = [...raw.matchAll(DECIMAL_AMOUNT)].filter((m) => Number(m[0].replace(/[^\d.,]/g, '').replace(',', '.')) === n).map((m) => [m.index, m.index + m[0].length]);
44
+ return decimals.length ? decimals : numberSpans(raw, n, true);
45
+ }
46
+ /** The answer with its amount's own digits blanked (same length, so spans still index the original). */
47
+ function amountMasked(raw) {
48
+ const spans = amountSpans(raw, amountInAnswer(raw));
49
+ return spans.reduce((text, [a, b]) => text.slice(0, a) + ' '.repeat(b - a) + text.slice(b), raw);
50
+ }
40
51
  /** Source intervals let different readers recognize the same evidence without agreeing on its type. */
41
52
  function numberSpans(raw, value, amount = false) {
42
53
  const runs = [...raw.matchAll(/\d(?:[\s.,-]?\d)*/g)];
@@ -117,19 +128,19 @@ export function readInputProfile(profile, answer, isCurrent = true) {
117
128
  }
118
129
  const kind = profile && profile.kind;
119
130
  if (kind === 'choice') {
131
+ // An amount's own digits are the amount's: a number beside it ("51:72.45", sub account 51 and $72.45) is still a choice.
132
+ const free = isAmountAnswer(raw) ? amountMasked(raw) : raw;
133
+ const runs = digitRuns(free), joined = free.replace(/\d(?:[\s.-]?\d)*/g, run => ' ' + run.replace(/\D/g, '') + ' ');
120
134
  const choices = (Array.isArray(profile.choices) ? profile.choices : []).filter(c => isCurrent || c.current_only !== true).map(c => ({ ...c,
121
135
  numericWords: (c.phrases ?? c.words ?? []).filter(word => /^\d{2,}$/.test(String(word).trim())),
122
- phrases: (c.phrases ?? c.words ?? []).concat(isCurrent ? c.keys || [] : []).filter(word => !(isAmountAnswer(raw) && /^\d+$/.test(String(word).trim())) && (isCurrent || profile.numeric_values === true || !/^[\d*#]$/.test(String(word).trim()))) }));
136
+ phrases: (c.phrases ?? c.words ?? []).concat(isCurrent ? c.keys || [] : []).filter(word => !(free !== raw && /^\d+$/.test(String(word).trim()) && !runs.includes(String(word).trim())) && (isCurrent || profile.numeric_values === true || !/^[\d*#]$/.test(String(word).trim()))) }));
123
137
  // Semantic numeric values may arrive in ASR groups ("95 52"), unlike menu keys.
124
138
  // Keep choice policy in pickChoice and source evidence in the original utterance.
125
- const runs = digitRuns(raw), joined = raw.replace(/\d(?:[\s.-]?\d)*/g, run => ' ' + run.replace(/\D/g, '') + ' ');
126
139
  const numericSpans = c => {
127
- if (isAmountAnswer(raw))
128
- return [];
129
140
  const words = c.numericWords.filter(word => runs.includes(String(word).trim()));
130
141
  if (!words.length || pickChoice(joined, [{ ...c, phrases: words }], false) !== c.key)
131
142
  return [];
132
- return words.flatMap(word => numberSpans(raw, String(word).trim()).filter(([a, b]) => raw.slice(a, b).replace(/\D/g, '') === String(word).trim()));
143
+ return words.flatMap(word => numberSpans(free, String(word).trim()).filter(([a, b]) => free.slice(a, b).replace(/\D/g, '') === String(word).trim()));
133
144
  };
134
145
  const base = pickChoice(raw, choices, false);
135
146
  const negative = c => ['no', 'decline', 'done', 'cancel'].includes(c.key) || c.negative === true;
@@ -160,7 +171,7 @@ export function readInputProfile(profile, answer, isCurrent = true) {
160
171
  if (inRange) {
161
172
  // the digits it was read from: the runs that start with its whole-dollar part ("82.50" said as "82 50" → 8250)
162
173
  const runs = digitRuns(raw).filter((d) => d.indexOf(String(Math.trunc(n))) === 0);
163
- return { value: String(n), spans: [...numberSpans(raw, n, true), ...(money?.cardinals ? readSpokenMoney(raw).filter(reading => reading.value === n).map(reading => [reading.start, reading.end]) : [])], evidence: runs.length ? runs.map((d) => `d:${d}`) : ['amount'] };
174
+ return { value: String(n), spans: [...amountSpans(raw, n), ...(money?.cardinals ? readSpokenMoney(raw).filter(reading => reading.value === n).map(reading => [reading.start, reading.end]) : [])], evidence: runs.length ? runs.map((d) => `d:${d}`) : ['amount'] };
164
175
  }
165
176
  else {
166
177
  return null;
@@ -196,7 +207,8 @@ export function readInputProfile(profile, answer, isCurrent = true) {
196
207
  else if (kind === 'digits') {
197
208
  const min = Number(profile.min || 1);
198
209
  const max = Number(profile.max || min);
199
- const fits = [...new Set(digitRuns(raw).filter((d) => d.length >= min && d.length <= max))];
210
+ // Out of order a decimal amount ("72.45", "287,15") is never this field's digits.
211
+ const fits = [...new Set(digitRuns(isCurrent ? raw : raw.replace(DECIMAL_AMOUNT, (m) => ' '.repeat(m.length))).filter((d) => d.length >= min && d.length <= max))];
200
212
  if (fits.length === 1) {
201
213
  return { value: fits[0], spans: numberSpans(raw, fits[0]), evidence: [`d:${fits[0]}`] };
202
214
  }
@@ -344,11 +356,15 @@ export function matchInputs(answer, candidates, current, choiceKey) {
344
356
  const pairedWithCurrent = (reading) => list.some((c) => isCur(c) && c.form === reading.form && ((c.confirm_against || []).includes(reading.name) || (fieldOf(reading).confirm_against || []).includes(c.name)));
345
357
  const inOrder = (reading) => reading.current || invited(reading) || pairedWithCurrent(reading);
346
358
  // Rule 3: a number read out of order must be whole: every digit run its evidence touches lies entirely inside it,
347
- // so the last four of "88 91 7112" fill nothing.
348
- const runs = [...raw.matchAll(/\d(?:[\s.-]?\d)*/g)].map((m) => [m.index, m.index + m[0].length]);
359
+ // so the last four of "88 91 7112" fill nothing. A written decimal is a run of its own ("51 72.45": 51, then 72.45).
360
+ const decimalRuns = [...raw.matchAll(DECIMAL_AMOUNT)].map((m) => [m.index, m.index + m[0].length]);
361
+ const undecimal = decimalRuns.reduce((text, [a, b]) => text.slice(0, a) + ' '.repeat(b - a) + text.slice(b), raw);
362
+ const runs = [...undecimal.matchAll(/\d(?:[\s.-]?\d)*/g)].map((m) => [m.index, m.index + m[0].replace(/\s+$/, '').length]).concat(decimalRuns);
349
363
  const wholeRuns = (reading) => runs.every(([a, b]) => !(reading.spans || []).some(([s, e]) => s < b && a < e) || (reading.spans || []).some(([s, e]) => s <= a && e >= b));
350
- // Rule 4: a bare number has no money mark and no word in its evidence.
351
- const bare = (reading) => !isAmountAnswer(raw) && (reading.spans || []).length > 0 && (reading.spans || []).every(([a, b]) => /\d/.test(raw.slice(a, b)) && !/\p{L}/u.test(raw.slice(a, b)));
364
+ // Rule 4: a bare number has no money mark and no word in its evidence. Written cents are a money mark: "51:72.45" at
365
+ // the sub account question is sub account 51 and the amount 72.45 (prod 2026-10-30).
366
+ const writtenCents = (reading) => (reading.spans || []).every(([a, b]) => decimalRuns.some(([c, d]) => c <= a && b <= d));
367
+ const bare = (reading) => !isAmountAnswer(raw) && (reading.spans || []).length > 0 && (reading.spans || []).every(([a, b]) => /\d/.test(raw.slice(a, b)) && !/\p{L}/u.test(raw.slice(a, b))) && !writtenCents(reading);
352
368
  // Rule 1 by shape: a bare number whose length is the current question's own keypad length (`digits`) answers that
353
369
  // question even when its reader rejects it (an expiration "1320" is month 13); its validation decides and the
354
370
  // question is asked again. It is never read out of order as another field (that "1320" is not a security code).
@@ -388,6 +404,37 @@ export function matchInputs(answer, candidates, current, choiceKey) {
388
404
  return true;
389
405
  }
390
406
  });
407
+ // Rule 4 for a compound answer: a bare number beside other words ("New card 100") is the invited amount even when the
408
+ // amount reader, which reads only a whole monetary answer, could not read it from the whole answer.
409
+ for (const name of invitedNames) {
410
+ const field = list.find((c) => c.name === name && c.profile.kind === 'amount' && !isCur(c));
411
+ const other = field && !readings.some((reading) => reading.form === field.form && reading.name === name) ? readings.find((reading) => reading.rule === 'out_of_order' && bare(reading)) : null;
412
+ const read = other ? readInputProfile(field.profile, spanText(other), true) : null;
413
+ if (read && !read.alternatives && !read.incompleteMoney) {
414
+ readings.push({ ...other, form: field.form, name: field.name, value: read.value, known: false, repeatedChoice: false, wholeChoice: false, choice: false, narrative: false, resetOn: [], keypad: false,
415
+ group: field.group, exclusive: field.exclusive === true, level: forms.indexOf(field.scope || field.form), current: false, confirm: Array.isArray(field.confirm_against) ? field.confirm_against : [], rule: 'invited' });
416
+ }
417
+ else {
418
+ // no invited amount, or it was read, or no bare number for it
419
+ }
420
+ }
421
+ // Written cents beside another field's reading ("51:72.45" at the sub account question): the cents are the open amount,
422
+ // read alone, since the amount reader reads only a whole monetary answer. Never alone: "I paid 120.00" is no answer.
423
+ const centsField = list.filter((c) => c.profile.kind === 'amount' && !readings.some((reading) => reading.form === c.form && reading.name === c.name));
424
+ const uncovered = decimalRuns.filter(([a, b]) => !readings.some((reading) => (reading.spans || []).some(([s, e]) => s < b && a < e)));
425
+ if (centsField.length === 1 && uncovered.length === 1 && decimalRuns.length === 1 && readings.some((reading) => (reading.spans || []).length)) {
426
+ const field = centsField[0], [a, b] = uncovered[0], read = readInputProfile(field.profile, raw.slice(a, b), isCur(field));
427
+ if (read && !read.alternatives && !read.incompleteMoney) {
428
+ readings.push({ form: field.form, name: field.name, value: read.value, known: false, repeatedChoice: false, wholeChoice: false, choice: false, narrative: false, resetOn: [], keypad: false, spans: [[a, b]], fallback: false,
429
+ group: field.group, exclusive: field.exclusive === true, level: forms.indexOf(field.scope || field.form), current: isCur(field), confirm: Array.isArray(field.confirm_against) ? field.confirm_against : [], rule: isCur(field) ? undefined : 'out_of_order' });
430
+ }
431
+ else {
432
+ // the cents are not an amount this field takes
433
+ }
434
+ }
435
+ else {
436
+ // no single open amount, no single uncovered written decimal, or nothing else read
437
+ }
391
438
  // Rule 4: a single digit the current question offers as a key is that key (the keypad reading wins its group below);
392
439
  // one it does not offer reaches the invited amount, whose own validation decides ("3 dollars?").
393
440
  const paired = (a, b) => a.confirm.indexOf(b.name) > -1 || b.confirm.indexOf(a.name) > -1;
@@ -1 +1 @@
1
- {"version":3,"file":"money.d.ts","sourceRoot":"","sources":["../../src/helpers/money.js"],"names":[],"mappings":"AAIA,gHAAgH;AAChH,wDAGC;AAkBD;8FAC8F;AAC9F,oEAIC;AACD,+FAA+F;AAC/F,0DAEC;AACD;oGACoG;AACpG,4DAGC;AAWD;;;GAGG;AACH,+DAYC;AACD,4FAA4F;AAC5F,iEAAsG"}
1
+ {"version":3,"file":"money.d.ts","sourceRoot":"","sources":["../../src/helpers/money.js"],"names":[],"mappings":"AAIA,gHAAgH;AAChH,wDAGC;AAqBD;8FAC8F;AAC9F,oEAIC;AACD,+FAA+F;AAC/F,0DAEC;AACD;oGACoG;AACpG,4DAGC;AAWD;;;GAGG;AACH,+DAYC;AACD,4FAA4F;AAC5F,iEAAsG"}
@@ -10,8 +10,11 @@ export function validateMoneyOptions(options) {
10
10
  // This is an input grammar, not a conversation classifier. Anything outside one complete
11
11
  // monetary answer belongs to DISPATCH, including refusals, corrections with context and reports. "157 95" is dollars and
12
12
  // cents said with no word between them (owner 2026-10-29); "95 52" is not (a card's last four read in pairs).
13
- const NUMERIC_MONEY = /^(?:\$\s*)?(?:\d{1,3}(?:[,.]\d{3})+|\d+)(?:[.,]\d{1,2})?(?:\s*(?:dollars?|d[oó]lar(?:es)?|bucks?))?(?:\s*(?:and|con|y)\s*(?:un\s*)?\d{1,2}(?:\s*(?:cents?|centavos?))?)?$|^\d{1,2}\s*(?:cents?|centavos?)$|^(?:\$\s*)?(?:\d|\d{3,})\s+\d{2}$/i;
13
+ const NUMERIC_MONEY = /^(?:\$\s*)?(?:\d{1,3}(?:[,.]\d{3})+|\d+)(?:[.,]\d{1,2})?(?:\s*(?:dollars?|d[oó]lar(?:es)?|bucks?|\$))?(?:\s*(?:and|con|y)\s*(?:un\s*)?\d{1,2}(?:\s*(?:cents?|centavos?))?)?$|^\d{1,2}\s*(?:cents?|centavos?)$|^(?:\$\s*)?(?:\d|\d{3,})\s+\d{2}$/i;
14
14
  const PAYMENT_PREFIX = /^(?:(?:i (?:want to|would like to|can|am able to|will) |i['’]d like to )?pay|(?:quiero |quisiera |puedo |s[ií] puedo |voy a )?(?:pagar|abonar)|(?:no,?\s+)?make it)\s*(?=\$|\d|[a-záéíóúüñ])/i;
15
+ // The field named with its value ("100 amount", "the amount is 100", "monto 100"): the amount, said as such.
16
+ const AMOUNT_LABEL_PREFIX = /^(?:(?:the|el|la)\s+)?(?:amount|monto|cantidad)(?:\s+(?:is|of|es|de))?[:\s]+(?=\$|\d)/i;
17
+ const AMOUNT_LABEL_SUFFIX = /(?<=\d|\$)\s+(?:(?:as\s+)?(?:the\s+)?amount|(?:de\s+|el\s+)?monto|(?:de\s+|la\s+)?cantidad)$/i;
15
18
  const ACCOUNT_PREFIX = /^(?:(?:sub[ -]?account|subcuenta|cuenta)\s+\d{1,8}\s*,\s*)/i;
16
19
  const DIGIT_WORD = '(?:zero|one|two|three|four|five|six|seven|eight|nine|cero|uno|dos|tres|cuatro|cinco|seis|siete|ocho|nueve)';
17
20
  const CARD_SUFFIX = new RegExp('\\s+(?:with (?:the |my )?card(?: (?:ending(?: in)?|number))?|con (?:la |mi )?tarjeta(?: (?:que termina en|terminad[ao] en))?)\\s+(?:\\d(?:[ -]?\\d){3}|' + DIGIT_WORD + '(?: ' + DIGIT_WORD + '){3})$', 'i');
@@ -19,7 +22,7 @@ const ACCOUNT_SUFFIX = /\s+(?:(?:on|to) (?:sub[ -]?account|account)|(?:a|en) (?:
19
22
  function completeMoneyAnswer(input) {
20
23
  let raw = String(input ?? '').replace(/\\(['"\\])/g, '$1').trim().replace(/^[¡!]+|[.!]+$/g, '').trim();
21
24
  raw = raw.replace(/^(?:please|por favor)[, ]+/i, '').replace(/[, ]+(?:please|por favor)$/i, '').trim();
22
- raw = raw.replace(PAYMENT_PREFIX, '').replace(ACCOUNT_PREFIX, '').replace(CARD_SUFFIX, '').replace(ACCOUNT_SUFFIX, '').trim();
25
+ raw = raw.replace(PAYMENT_PREFIX, '').replace(AMOUNT_LABEL_PREFIX, '').replace(AMOUNT_LABEL_SUFFIX, '').replace(ACCOUNT_PREFIX, '').replace(CARD_SUFFIX, '').replace(ACCOUNT_SUFFIX, '').trim();
23
26
  if (NUMERIC_MONEY.test(raw))
24
27
  return true;
25
28
  const { readings, invalid } = scanSpokenMoney(raw);
@@ -23,6 +23,9 @@
23
23
  import type { FlowParameterDefinition } from '../index.js';
24
24
  /** A noul at or above this is a yes. */
25
25
  export declare const STAGE_YES_THRESHOLD = 0.5;
26
+ /** Stage 1: a start below this (`any`) whose route gives its top flow less than SPLIT_ROUTE_SHARE is "None" (owner 2026-10-29). */
27
+ export declare const WEAK_START = 0.55;
28
+ export declare const SPLIT_ROUTE_SHARE = 0.7;
26
29
  export interface StageTurn {
27
30
  role: 'user' | 'assistant';
28
31
  content: string;
@@ -112,7 +115,9 @@ export declare function decodeFlowLlm(text: string, stage: FlowStageRequest): {
112
115
  };
113
116
  /**
114
117
  * System One's stage-1 answers as the canonical reply. `any` at or above the threshold starts the flow `route`
115
- * chose; below it, "None". `scores` are the top three flows by the chance that each is the one started,
118
+ * chose; below it, "None". A weak start (`any` below WEAK_START) is "None" too when the route is split, its top flow
119
+ * under SPLIT_ROUTE_SHARE: unsure a flow is wanted and unsure which, the Guru answers (prod 2026-10-29: 'Esta
120
+ * cancelación afecta mi crédito?' after a cancellation, any 0.51, restarted CancelService). `scores` are the top three flows by the chance that each is the one started,
116
121
  * any × its route probability (for logs and the host's near-miss hedge); `any` is logged with them. Throws when
117
122
  * either answer is missing or names no offered flow.
118
123
  */
@@ -1 +1 @@
1
- {"version":3,"file":"intent-stages.d.ts","sourceRoot":"","sources":["../../src/host/intent-stages.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"intent-stages.d.ts","sourceRoot":"","sources":["../../src/host/intent-stages.ts"],"names":[],"mappings":"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"}
@@ -22,6 +22,9 @@
22
22
  */
23
23
  /** A noul at or above this is a yes. */
24
24
  export const STAGE_YES_THRESHOLD = 0.5;
25
+ /** Stage 1: a start below this (`any`) whose route gives its top flow less than SPLIT_ROUTE_SHARE is "None" (owner 2026-10-29). */
26
+ export const WEAK_START = 0.55;
27
+ export const SPLIT_ROUTE_SHARE = 0.7;
25
28
  // ── the rules: written once, rendered for both executors ─────────────────────────────────────────────
26
29
  const FLOW_TRUE = "The latest message, read with the conversation, is what the workflow's description says should start it: the user asks for its action or information, or raises the problem it exists to handle. Also when it accepts the assistant's offer in the conversation to start or resume it (assistant: \"Shall I try the payment again?\" → user: \"yes\").";
27
30
  const FLOW_FALSE = "It only shares a topic with the workflow, is not what the workflow's description covers, or is a greeting, an acknowledgement or a fragment that asks for nothing.";
@@ -31,6 +34,8 @@ const PARAMETER_RULES = [
31
34
  'Give a value only when the user gave it, in the latest message or earlier in the conversation. Never guess.',
32
35
  "Write it as the parameter's rule says; otherwise the value alone, in the user's words (never the whole sentence around it).",
33
36
  'A short answer to the assistant\'s last message ("1", "yes", "the first one") chooses what that message offered.',
37
+ 'A plain yes to the assistant\'s last message, when it asked the user to confirm values, gives those values as the user\'s own (assistant: "You want to pay $40 to sub account 80?" → user: "yes": 40 and 80; "¿Desea pagar $40 a la subcuenta 80?" → "sí": 40 and 80). A yes to a question that confirmed no value gives none ("Your payment of $40 was successful. Anything else?" → "yes": none).',
38
+ 'When the user gave a value more than once, the latest one counts.',
34
39
  'A value the user did not give is null.',
35
40
  ];
36
41
  const ENUM_TRUE = (value) => `The parameter's rule makes \`${value}\` its value for what the user said.`;
@@ -110,7 +115,9 @@ export function decodeFlowLlm(text, stage) {
110
115
  }
111
116
  /**
112
117
  * System One's stage-1 answers as the canonical reply. `any` at or above the threshold starts the flow `route`
113
- * chose; below it, "None". `scores` are the top three flows by the chance that each is the one started,
118
+ * chose; below it, "None". A weak start (`any` below WEAK_START) is "None" too when the route is split, its top flow
119
+ * under SPLIT_ROUTE_SHARE: unsure a flow is wanted and unsure which, the Guru answers (prod 2026-10-29: 'Esta
120
+ * cancelación afecta mi crédito?' after a cancellation, any 0.51, restarted CancelService). `scores` are the top three flows by the chance that each is the one started,
114
121
  * any × its route probability (for logs and the host's near-miss hedge); `any` is logged with them. Throws when
115
122
  * either answer is missing or names no offered flow.
116
123
  */
@@ -128,7 +135,8 @@ export function decodeFlowSystemOne(answers, stage, { threshold = STAGE_YES_THRE
128
135
  const probabilities = Object.fromEntries(names.map(n => [n, Number(raw[n]) || 0]));
129
136
  const ranked = names.map(n => ({ name: n, p: any * probabilities[n] })).sort((a, b) => b.p - a.p);
130
137
  const scores = ranked.slice(0, 3);
131
- const flowName = any >= threshold && ranked[0] ? ranked[0].name : 'None';
138
+ const split = any < WEAK_START && !!ranked[0] && probabilities[ranked[0].name] < SPLIT_ROUTE_SHARE;
139
+ const flowName = any >= threshold && ranked[0] && !split ? ranked[0].name : 'None';
132
140
  return { flowName, scores, any };
133
141
  }
134
142
  /**
@@ -1 +1 @@
1
- export const JSFE_RUNTIME_FINGERPRINT = "sha256:98ad4bedaa095bb4156697534368f0735249b4ca57cb6b9c612581e34831e864";
1
+ export const JSFE_RUNTIME_FINGERPRINT = "sha256:fd2a9a936f409b3e5eaf40eb2c60a9f081caa099852cbd8fabdb5135a862d5f2";
@@ -132,6 +132,7 @@ A form declaration has top-level common fields and `form:{inputs:[...]}`. Do not
132
132
  | `prompt`, `prompt_es` | UI labels. |
133
133
  | `incomplete_outcome` | Explicit business outcome fallback for incomplete optional followups; see recovery. |
134
134
  | `on_exhausted` | Bounded recovery worker after validation attempts are exhausted. |
135
+ | `on_no` | Worker (called with `{form}`) for a whole NO at one of the form's questions, or a sub-form's, that the question does not read itself (not a yes/no, confirmation or proposal question, and no choice of it reads NO); the form then continues. A payment declares one that starts its selection again. |
135
136
 
136
137
  Top-level `variables` provides extra form locals. JSFE creates instance IDs, saved arguments, values, validation and confirmation state; do not author copies of these bookkeeping fields.
137
138
 
@@ -255,6 +256,8 @@ An unreadable answer with any words goes to the reply ranker, offered the questi
255
256
 
256
257
  `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.
257
258
 
259
+ A choice marked `current_only` is read only at its own question: neither the word reader nor the reply ranker offers it while another question is asked (a route's `done` or `switch` is never picked for a NO or a sub account said at the amount question). Written cents (`72.45`, `$72.45`, `287,15`) are an amount by shape: out of order they fill an open amount, and beside another field's reading (`51:72.45` at a sub account question) they are that amount, never digits of a security code or ZIP code.
260
+
258
261
  Use `response:{description}` for open descriptions that need a model's interpretation. The prepared question and field description become the contextual DISPATCH question, and the reply ranker decides what the reply does: an answer is extracted and still passes deterministic validation (missing usable data yields with progress saved); a withdrawal leaves for the host; a new request routes through intent detection. No separate field classifier/model role is needed. Do not attempt exhaustive conversational inference with keyword lists.
259
262
 
260
263
  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):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsfe",
3
- "version": "1.15.7",
3
+ "version": "1.15.9",
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",