jsfe 1.11.1 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../../src/forms/flows.js"],"names":[],"mappings":"AA4HA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4DC;AACD;;;GAGG;AACH;;;;;;EAGC;AAmbD,wHAAwH;AACxH,wEAaC;AACD,4FAA4F;AAC5F;;;EAYC;AACD,sGAAsG;AACtG;;;EAOC;AA9mBM,6CAAuG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsyB9G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAOG"}
1
+ {"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../../src/forms/flows.js"],"names":[],"mappings":"AA4HA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4DC;AACD;;;GAGG;AACH;;;;;;EAGC;AAgcD,wHAAwH;AACxH,wEAaC;AACD,4FAA4F;AAC5F;;;EAYC;AACD,sGAAsG;AACtG;;;EAOC;AA3nBM,6CAAuG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmzB9G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAOG"}
@@ -411,6 +411,17 @@ const readFormChoice = {
411
411
  SIDE('choice_clear', 'clearFormMisses(cargo,form,input)'),
412
412
  FLOW('choice_accept', 'collect-input-accept', 'call', { hits: '{{reading.hits}}' })]
413
413
  };
414
+ // A confirmation's unread answer goes to the reply ranker with the confirmation question (owner 2026-10-27), never
415
+ // straight to the host. The ranker is offered NO only: a confirmation is accepted only by its exact YES. Result:
416
+ // cargo.confirmRanked = 'no' | 'none' (ConfirmForm reads it); `abort` ends every flow; `new_request` goes to intent detection.
417
+ const readConfirmReply = {
418
+ id: 'read-confirm-reply', name: 'ReadConfirmReply', version: '1.0.0', scope: 'isolated', description: 'Rank an unreadable answer to a confirmation: it declines (NO) or is asked again; a withdrawal or a new request leaves with saved form progress. Never confirms.',
419
+ parameters: [{ name: 'form', type: 'string' }, { name: 'question', type: 'string' }, { name: 'question_es', type: 'string' }],
420
+ variables: vars({ question: '', question_es: '', asked: '', reply: null }),
421
+ steps: [SET('confirm_asked', 'asked', "language === 'es' && question_es ? question_es : question"),
422
+ { ...yieldDispatch('confirm_dispatch', 'form', "'confirmation_not_matched'"), onResponse: 'continue', responseContext: 'Current question: {{asked}}', responseParameters: [{ name: 'key', type: 'string', description: "'no' when the reply declines or refuses what the question asks to confirm. Leave it empty otherwise, including when it agrees." }], responseVariable: 'reply' },
423
+ SIDE('confirm_ranked', "(cargo.confirmRanked=String(reply && reply.key || '').trim().toLowerCase()==='no' ? 'no' : 'none',true)")]
424
+ };
414
425
  const collectContextualAnswer = formFlow({ id: 'collect-contextual-answer', name: 'CollectContextualAnswer', description: 'Collect one open-ended answer using contextual DISPATCH and preserve the caller wording.',
415
426
  parameters: [...['question', 'question_es', 'description', 'context', 'context_es'].map(name => ({ name, type: 'string' })), { name: 'choices', type: 'array' }, { name: 'digits', type: 'object' }],
416
427
  extraVars: { question: '', question_es: '', description: '', context: 'your request', context_es: 'su solicitud', choices: [], digits: null },
@@ -495,11 +506,11 @@ const disambiguate = {
495
506
  // ── confirm-form: the exact confirmation of a finished form; a correction is matched against the form chain ───────
496
507
  const confirmForm = {
497
508
  id: 'confirm-form', name: 'ConfirmForm', version: '1.0.0', scope: 'isolated',
498
- 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).",
509
+ 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 unread one goes to the reply ranker (read-confirm-reply). Result: cargo.formConfirm[form] = 'yes' | 'no' | 'changed'. Params: form, question(_es), exit_context(_es).",
499
510
  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" }]),
500
511
  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: '' }),
501
512
  steps: [
502
- SIDE('cf_reset', "(cargo.formConfirm = cargo.formConfirm || {}, cargo.formConfirm[form] = '', true)"),
513
+ SIDE('cf_reset', "(cargo.formConfirm = cargo.formConfirm || {}, cargo.formConfirm[form] = '', cargo.confirmRanked = '', true)"),
503
514
  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};}))`),
504
515
  SET('cf_initial_chain', 'cf_chain', CHAIN_JS),
505
516
  SET('cf_amount_context', 'cf_amount', "cf_chain.some(function(input){return input.profile && input.profile.kind==='amount';})"),
@@ -534,9 +545,11 @@ const confirmForm = {
534
545
  'condition: cf_named': SET('cf_named_reset', 'cf_result', "(resetFormInputs(cargo, cf_named.form, [cf_named.name]), 'changed')"),
535
546
  "condition: cargo.choice === 'no'": SET('cf_declined', 'cf_result', "'no'"),
536
547
  "condition: Array.isArray(clarifications) && pickChoice(String(cargo.choiceText || '').replace(/[?¿]/g,''),[{key:'clarify',phrases:clarifications,exact:true}],false)==='clarify'": SET('cf_clarification', 'cf_result', "'clarification'"),
537
- 'condition: !cf_must && (cf_words >= 3 || cf_amount && cf_words > 0)': yieldDispatch('cf_elsewhere', 'form', "'confirmation_not_matched'"),
548
+ 'condition: !cf_must && (cf_words >= 3 || cf_amount && cf_words > 0)': FLOW('cf_elsewhere', 'read-confirm-reply', 'call', { form: '{{form}}', question: '{{cf_prompt}}', question_es: '{{cf_prompt_es}}' }),
538
549
  default: SET('cf_unclear', 'cf_result', "'unclear'")
539
550
  } },
551
+ // the ranker's reading of an unread answer: a decline is the NO; an answer that declines nothing is asked again
552
+ SET('cf_ranked', 'cf_result', "cf_result ? cf_result : cargo.confirmRanked === 'no' ? 'no' : cargo.confirmRanked === 'none' ? 'unclear' : ''"),
540
553
  SET('cf_changed', 'cf_result', "cf_result ? cf_result : 'changed'"),
541
554
  // an unclear short answer is asked again (the form runs again and reaches this question), at most twice
542
555
  SET('cf_tries_count', 'cf_tries', "cf_result === 'unclear' ? missFormInput(cargo,form,'__confirm') : (clearFormMisses(cargo,form,'__confirm'),0)"),
@@ -826,7 +839,7 @@ const resumeForm = { id: 'resume-form', name: 'ResumeForm', version: '1.0.0', sc
826
839
  SET('resume_definition', 'target', 'cargo.forms[form].definition'), SET('resume_parent', 'parent', "cargo.forms[form].parent || ''"), SET('resume_slot', 'slot', "cargo.forms[form].result_input || ''"),
827
840
  FLOW('resume_owned_form', '{{target}}', 'replace', { form_instance: '{{form}}', parent_form: '{{parent}}', result_input: '{{slot}}' })
828
841
  ] };
829
- const LIBRARY_FLOWS = [readFormReply, readFormChoice, confirmInputProposal, proposalDeclined, resumeForm, performAction, sendTextAction, prepareInput, runForm, collectInput, validateBatch, requestCancelled, readFormResponse, collectContextualAnswer, accept, acceptedEffects, disambiguate, confirmForm, formDone, providerMenu, ...validators, ...fieldCollectors, ...subForms, provideData, provideQueuedData, loadSource, answerFacts];
842
+ const LIBRARY_FLOWS = [readFormReply, readFormChoice, readConfirmReply, confirmInputProposal, proposalDeclined, resumeForm, performAction, sendTextAction, prepareInput, runForm, collectInput, validateBatch, requestCancelled, readFormResponse, collectContextualAnswer, accept, acceptedEffects, disambiguate, confirmForm, formDone, providerMenu, ...validators, ...fieldCollectors, ...subForms, provideData, provideQueuedData, loadSource, answerFacts];
830
843
  const LIBRARY_CONTRACTS = {
831
844
  'confirm-input-proposal': { role: 'collector', requires: [INPUT_PROPOSALS_REQUIREMENT] }, 'confirm-input-proposal-declined': { role: 'worker', requires: [INPUT_PROPOSALS_REQUIREMENT] },
832
845
  'resume-form': { role: 'worker' },
@@ -835,7 +848,7 @@ const LIBRARY_CONTRACTS = {
835
848
  'prepare-input': { role: 'worker' }, 'run-form': { role: 'worker' }, 'collect-input': { role: 'worker' },
836
849
  'request-cancelled': { role: 'worker' },
837
850
  'read-form-response': { role: 'worker', requires: [CONTEXTUAL_RESPONSES_REQUIREMENT] },
838
- 'read-form-reply': { role: 'worker' }, 'read-form-choice': { role: 'worker', requires: [CONTEXTUAL_RESPONSES_REQUIREMENT] },
851
+ 'read-form-reply': { role: 'worker' }, 'read-form-choice': { role: 'worker', requires: [CONTEXTUAL_RESPONSES_REQUIREMENT] }, 'read-confirm-reply': { role: 'worker', requires: [CONTEXTUAL_RESPONSES_REQUIREMENT] },
839
852
  'collect-contextual-answer': { role: 'collector', requires: [CONTEXTUAL_RESPONSES_REQUIREMENT] },
840
853
  'validate-input-batch': { role: 'worker', result: { path: 'cargo.inputBatch', type: 'object' } },
841
854
  'collect-input-accept': { role: 'worker' }, 'input-accepted-effects': { role: 'worker' },
@@ -1 +1 @@
1
- export const JSFE_RUNTIME_FINGERPRINT = "sha256:25d1acd637baced87978aa81eb3c47112eb8d82d1021dfb3fedc68aba0b8c988";
1
+ export const JSFE_RUNTIME_FINGERPRINT = "sha256:d2fe25c463500cc6a524418c9ccb5ac710130ac6cce764894ca522f6fe83a22c";
@@ -242,3 +242,13 @@ counted as an attempt, so `max_attempts` still bounds it; `abort` ends every flo
242
242
  detection. A host fixture that answered such turns with intent detection only must now also answer `rank_reply`.
243
243
 
244
244
  **A press verb before a key picks it:** "ya oprimí el uno", "I already pressed one", "marqué el 2" (not after a negation).
245
+
246
+ ## 1.12.0 — a confirmation's unread answer goes to the reply ranker
247
+
248
+ ConfirmForm no longer hands a long unread answer (three or more words, or any words when the form holds an amount) to the
249
+ host with a plain DISPATCH. The new shared worker `read-confirm-reply` ranks it against the confirmation question:
250
+ `answers` with the reply declining (stage-2 `key` = `no`) is the confirmation's NO, so the form's decline path runs;
251
+ `answers` with nothing declined asks the confirmation again, counted as a miss (two misses still hand off, as before);
252
+ `abort` ends every flow; `new_request` goes to intent detection. The ranker is offered NO only: a confirmation is accepted
253
+ only by its exact YES, never by a ranked reading. Hosts that ship the shared workers in a system set must add
254
+ `read-confirm-reply`; a host fixture that answered such turns with intent detection only must now also answer `rank_reply`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsfe",
3
- "version": "1.11.1",
3
+ "version": "1.12.0",
4
4
  "description": "TypeScript workflow engine for conversational/automation apps: multi-flow orchestration, interrupt/resume, stack-based control, robust errors, and flexible tool/REST integration.",
5
5
  "license": "MIT",
6
6
  "author": "Ron Pinkas",