jsfe 1.17.1 → 1.18.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.
@@ -16,5 +16,6 @@ export function identityResumeContext(forms: any, owner: any, target: any, ident
16
16
  parent_form: any;
17
17
  result_input: any;
18
18
  };
19
+ export function verificationFailedText(cargo: any, ended: any, es?: boolean, part?: string): string;
19
20
  export const FLOWS: any[];
20
21
  //# sourceMappingURL=identity-flows.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"identity-flows.d.ts","sourceRoot":"","sources":["../../src/forms/identity-flows.js"],"names":[],"mappings":"AA6BA,+GAA+G;AAC/G,oFAOC;AAQD,wFAAwF;AACxF,iDAGC;AACD,6CAMC;AACD,qHAQC;AAMD,4EAKC;AAwFD,2DAOC;AACD,4DAOC;AACD,gHAAgH;AAChH,8DAGC;AA0ID,8EAGC;AA4BD,4FAA4F;AAC5F;;;;EAOC;AAQD,0BAA4I"}
1
+ {"version":3,"file":"identity-flows.d.ts","sourceRoot":"","sources":["../../src/forms/identity-flows.js"],"names":[],"mappings":"AA6BA,+GAA+G;AAC/G,oFAOC;AAQD,wFAAwF;AACxF,iDAGC;AACD,6CAMC;AACD,qHAQC;AAMD,4EAKC;AAwFD,2DAOC;AACD,4DAOC;AACD,gHAAgH;AAChH,8DAGC;AA4ID,8EAGC;AA4BD,4FAA4F;AAC5F;;;;EAOC;AAWD,oGAOC;AAaD,0BAAmJ"}
@@ -148,8 +148,8 @@ const contact = worker('get-cell-or-email', 'GetCellOrEmail', 'Identify a suppli
148
148
  const declined = worker('contact-caller-declined', 'ContactCallerDeclined', 'Repeat contact collection without offering caller ID.', { parameters: [param('contact_options', 'Current contact service options', 'object')], steps: [set('contact_declined_options', 'contact_options', "Object.assign({},contact_options,{caller_on_file:'no',intro:'',intro_es:''})"), contactReplay('contact_declined_repeat')] });
149
149
  const splitFailed = worker('contact-split-failed', 'ContactSplitFailed', 'Restart a split identifier after too many digits.', { parameters: [param('contact_options', 'Current contact service options', 'object')], steps: [set('contact_split_options', 'contact_options', "Object.assign({},contact_options,{caller_on_file:'no',intro:cargo.voice?'I could not put that number together. Please enter it on your keypad.':'I could not put that number together. Please type the whole number.',intro_es:cargo.voice?'No pude juntar ese número. Por favor ingréselo con el teclado.':'No pude juntar ese número. Por favor escriba el número completo.'})"), contactReplay('contact_split_repeat')] });
150
150
  const rejected = worker('contact-not-on-file', 'ContactNotOnFile', 'Re-collect a rejected account contact, then offer the common retry menu at the rejection limit.', { parameters: [param('contact_options', 'Current contact service options', 'object'), param('error_message', 'Rejection sentence'), param('error_message_es', 'Spanish rejection sentence')], steps: [set('contact_rejected_options', 'contact_options', "Object.assign({},contact_options,{caller_on_file:cargo.contactKind === 'caller' || contact_options.caller_on_file === 'no' ? 'no':'',fromMenu:false,intro:error_message,intro_es:error_message_es})"), when('contact_rejected_again', 'cargo.gceRejects <= 2', contactReplay('contact_rejected_collect', { gce_rejected: true })), set('contact_rejected_error', 'contact_error', 'error_message'), set('contact_rejected_error_es', 'contact_error_es', 'error_message_es'), contactMenu('contact_rejected_menu')] });
151
- const AUTH_DEFAULTS = { retry_flow: '', cancel_flow: '', cell_number: '', email: '', email_validator: '', cell_validator: '', code_validator: '', answer_handler: '', retry_returns: false, retrying: false, declined_caller: false, parent_form: '', contact_valid: false, auth_result: false, auth_contact_error: '', auth_contact_error_es: '', auth_previous_options: null, auth_restart: false, auth_join: false, otp_expiry_ms: OTP_EXPIRY_MS };
152
- const authKeys = ['retry_flow', 'cancel_flow', 'email_validator', 'cell_validator', 'code_validator', 'answer_handler', 'retry_returns', 'parent_form'];
151
+ const AUTH_DEFAULTS = { retry_flow: '', cancel_flow: '', cell_number: '', email: '', email_validator: '', cell_validator: '', code_validator: '', answer_handler: '', retry_returns: false, retrying: false, declined_caller: false, parent_form: '', task: '', task_es: '', failed_flow: '', contact_valid: false, auth_result: false, auth_contact_error: '', auth_contact_error_es: '', auth_previous_options: null, auth_restart: false, auth_join: false, otp_expiry_ms: OTP_EXPIRY_MS };
152
+ const authKeys = ['retry_flow', 'cancel_flow', 'email_validator', 'cell_validator', 'code_validator', 'answer_handler', 'retry_returns', 'parent_form', 'task', 'task_es', 'failed_flow'];
153
153
  const auth = worker('authenticate-user', 'AuthenticateUser', 'Authenticate a contact using shared FORM collection and one-time-code services.', { parameters: Object.keys(AUTH_DEFAULTS).filter(k => !['contact_valid', 'auth_result', 'auth_contact_error', 'auth_contact_error_es', 'auth_previous_options', 'auth_restart', 'auth_join', 'otp_expiry_ms'].includes(k)).map(k => param(k, k.replace(/_/g, ' '), ['retry_returns', 'retrying', 'declined_caller'].includes(k) ? 'boolean' : 'string')).concat([param('caller_on_file', 'Caller-ID eligibility inherited from the invoking service')]), variables: vars(AUTH_DEFAULTS), contract: { role: 'gate', summary: 'Generic one-time-code identity gate', result: { paths: ['cargo.otpVerified', 'cargo.authRetry'], type: 'object' } }, steps: [
154
154
  set('auth_restart', 'auth_restart', "retrying === true || retrying === 'true'"),
155
155
  set('auth_previous_options', 'auth_previous_options', 'cargo.authOptions ? Object.assign({},cargo.authOptions) : null'),
@@ -222,6 +222,8 @@ const code = worker('get-and-validate-otp-code', 'GetAndValidateOtpCode', 'Colle
222
222
  set('otp_answer', 'otp_code', "String(cargo.serviceAnswer.values.code || cargo.serviceAnswer.raw || '')"), set('otp_choice', 'user_choice', 'otp_code.trim().toLowerCase()'), set('otp_digits', 'normalized_otp_code', expression(otpDigits, 'otp_code')),
223
223
  call('otp_validate', 'validate-otp-code', undefined, 'replace')
224
224
  ] });
225
+ // A verification that ran out of attempts ends cleanly (owner 2026-10-30): no retry question is left open for the next turn.
226
+ const verificationEnd = id => call(id, 'verification-failed', { form: '{{cargo.authOptions.parent_form}}' }, 'reboot');
225
227
  function otpMenu(id, error, error_es, allow_code_entry = false) { const en = id + '_message', es = id + '_message_es'; otpMenuMessages.push([en, error], [es, error_es]); return call(id, 'generic-retry-with-options', { error_message: '{{' + en + '}}', error_message_es: '{{' + es + '}}', retry_flow: 'authenticate-user', retry_call_type: 'replace', cancel_flow: '{{cargo.authCancelFlow}}', allow_code_entry, capture_patterns: '{{cargo.authCapture}}', parent_form: '{{cargo.authOptions.parent_form}}', question: 'Would you like to try again to get access to your account?', question_es: '¿Le gustaría intentar nuevamente obtener acceso a su cuenta?' }, 'replace'); }
226
228
  const OTP_WORDS = {
227
229
  other: ['otra cuenta', 'otro número', 'otro numero', 'cuenta diferente', 'another account', 'different account', 'other account', 'another number', 'different number', 'not my account', 'no es mi cuenta', 'número de cuenta', 'numero de cuenta', 'account number'],
@@ -301,19 +303,19 @@ const validateCode = worker('validate-otp-code', 'ValidateOtpCode', 'Validate a
301
303
  default: effect('otp_reply_route_none', 'true')
302
304
  }),
303
305
  effect('otp_word_count', '(cargo.otpWordMisses=Number(cargo.otpWordMisses || 0)+(no_code_given?1:0),true)'),
304
- when('otp_word_cap', 'no_code_given && cargo.otpWordMisses >= 3', otpMenu('otp_word_menu', "I still don't have the code. To reach a person, {{cargo.verb}} AGENT.", 'Todavía no tengo el código. Para hablar con una persona, {{cargo.verb_es}} AGENTE.', true)),
306
+ when('otp_word_cap', 'no_code_given && cargo.otpWordMisses >= 3', verificationEnd('otp_word_end')),
305
307
  // a reason for the call authentication serves is acknowledged: verification comes first
306
308
  when('otp_usecase_ack', "no_code_given && otp_reply && String(otp_reply.key || '').indexOf('usecase:') === 0", say('otp_usecase_explain', 'Understood. First we need to finish verifying your identity. I sent you a message with a six-digit code in it. Read those six digits back to me. You can also {{cargo.verb}} AGENT to reach a person, or EXIT to cancel.', "Entendido. Primero necesitamos completar la verificación de su identidad. Le envié un mensaje con un código de seis dígitos. Dígame esos seis dígitos. También puede {{cargo.voice ? 'decir' : 'escribir'}} AGENTE para hablar con una persona, o SALIR para cancelar.")),
307
309
  when('otp_explanation', "no_code_given && !(no_code_given && otp_reply && String(otp_reply.key || '').indexOf('usecase:') === 0)", say('otp_explain', 'Let me explain. I sent you a message with a six-digit code in it. Read those six digits back to me. You can also {{cargo.verb}} AGENT to reach a person, or EXIT to cancel.', "Le explico. Le envié un mensaje con un código de seis dígitos. Dígame esos seis dígitos. También puede {{cargo.voice ? 'decir' : 'escribir'}} AGENTE para hablar con una persona, o SALIR para cancelar.")),
308
310
  effect('otp_format_count', '(cargo.otpFormatMisses=Number(cargo.otpFormatMisses || 0)+(normalized_otp_code.length > 0 && normalized_otp_code.length !== 6?1:0),true)'),
309
- when('otp_format_cap', 'normalized_otp_code.length !== 6 && cargo.otpFormatMisses >= 3', otpMenu('otp_format_menu', "I still don't have a 6-digit code. To reach a person, {{cargo.verb}} AGENT.", 'Todavía no tengo un código de 6 dígitos. Para hablar con una persona, {{cargo.verb_es}} AGENTE.', true)),
311
+ when('otp_format_cap', 'normalized_otp_code.length !== 6 && cargo.otpFormatMisses >= 3', verificationEnd('otp_format_end')),
310
312
  when('otp_format_retry', 'normalized_otp_code.length !== 6', call('otp_collect_again', 'get-and-validate-otp-code', undefined, 'replace')),
311
313
  effect('otp_mismatch_count', "(cargo.otpMismatches=Number(cargo.otpMismatches || 0)+(!cargo.otpVerified && cargo.otpFailure === 'mismatch'?1:0),true)"),
312
314
  when('otp_mismatch_cap', "!cargo.otpVerified && cargo.otpFailure === 'mismatch' && cargo.otpMismatches >= 3", effect('otp_withdraw', '(cargo.otpHash=null,cargo.otpAltHash=null,cargo.otpAltTimestamp=null,true)')),
313
315
  cases('otp_verified_result', {
314
316
  'condition: cargo.otpVerified === true': effect('otp_verified_record', '(cargo.otpLateCode=String(normalized_otp_code),true)'),
315
317
  "condition: cargo.otpFailure === 'expired' || cargo.otpFailure === 'missing'": call('otp_refresh', 'resend-otp-code', { resend_reason: '{{cargo.otpFailure}}' }),
316
- "condition: cargo.otpFailure === 'mismatch' && cargo.otpMismatches >= 3": otpMenu('otp_mismatch_menu', "That code didn't match three times, so I can't accept another try with it. To reach a person, {{cargo.verb}} AGENT.", 'Ese código no coincidió tres veces, así que no puedo aceptar otro intento con él. Para hablar con una persona, {{cargo.verb_es}} AGENTE.', false),
318
+ "condition: cargo.otpFailure === 'mismatch' && cargo.otpMismatches >= 3": verificationEnd('otp_mismatch_end'),
317
319
  default: say('otp_mismatch', "That code didn't match. Let's try again.", 'Ese código no coincide. Intentemos de nuevo.')
318
320
  }),
319
321
  set('otp_invite_text', 'invite_text', "language === 'es' ? typeof global_whatsapp_invite_es !== 'undefined' ? global_whatsapp_invite_es : '' : typeof global_whatsapp_invite !== 'undefined' ? global_whatsapp_invite : ''"),
@@ -397,4 +399,29 @@ const resume = worker('resume-identity-caller', 'ResumeIdentityCaller', 'Resume
397
399
  steps: [set('identity_resume_context', 'resume_context', expression(identityResumeContext, 'cargo.forms', 'parent_form', 'retry_flow', '{acct_number:acct_number,cell_number:cell_number,email:email}')),
398
400
  call('identity_resume_caller', '{{retry_flow}}', { form_instance: '{{resume_context.form_instance}}', parent_form: '{{resume_context.parent_form}}', result_input: '{{resume_context.result_input}}', gate_options: '{{gate_options}}', cell_number: '{{cell_number}}', email: '{{email}}', acct_number: '{{acct_number}}' }, 'replace')]
399
401
  });
400
- export const FLOWS = [taskGate, resume, service, rawTransport, contact, declined, splitFailed, rejected, auth, code, validateCode, emailToo, retry, email];
402
+ // The closing a verification out of attempts says: the task it was for (authenticate-user's task), and that the caller can try
403
+ // again once the code can reach them (owner wording 2026-10-30); part 'agent' is its last sentence, said after the tenant's
404
+ // failed_flow. An unknown action result keeps its hold notice, with no agent sentence.
405
+ export function verificationFailedText(cargo, ended, es = false, part = '') {
406
+ const hold = !!(ended && ended.recovery && ended.recovery.mode === 'hold');
407
+ if (part === 'agent')
408
+ return hold ? '' : es ? 'Para hablar con una persona, ' + cargo.verb_es + ' AGENTE.' : 'To reach a person, ' + cargo.verb + ' AGENT.';
409
+ if (hold)
410
+ return es ? 'El resultado de su solicitud anterior sigue sin confirmarse. Nuestro equipo debe revisarlo antes de otro intento.' : 'The result of your earlier request is still unknown. Our team needs to check it before another attempt.';
411
+ const task = String((cargo.authOptions && (es ? cargo.authOptions.task_es : cargo.authOptions.task)) || '');
412
+ return es ? 'No pude verificar su cuenta, así que no puedo ' + (task || 'continuar') + ' en este momento. Cuando pueda recibir el código para esta cuenta, podemos intentarlo de nuevo.'
413
+ : "I wasn't able to verify your account, so I can't " + (task || 'continue') + ' right now. Whenever you have access to receive the code for this account, we can try again.';
414
+ }
415
+ const failed = worker('verification-failed', 'VerificationFailed', 'End a verification that ran out of attempts: retire the owning task, set cargo.verificationFailed, report it unresolved (verification_failed), say so, run the failed_flow given to authenticate-user, then offer an agent.', {
416
+ parameters: [param('form', 'Owning task FORM instance')], variables: vars({ form: '', ended: null, vf_text: '', vf_text_es: '', vf_agent: '', vf_agent_es: '' }),
417
+ contract: { role: 'worker', summary: 'Clean end of a failed verification' },
418
+ steps: [set('vf_retire', 'ended', "form && cargo.forms && cargo.forms[form] ? cancelFormTask(cargo,form) : null"),
419
+ effect('vf_clear_code', "(cargo.otpHash=null,cargo.otpTimestamp=null,cargo.otpAltHash=null,cargo.otpAltTimestamp=null,cargo.verificationFailed=true,true)"),
420
+ set('vf_text', 'vf_text', expression(verificationFailedText, 'cargo', 'ended')), set('vf_text_es', 'vf_text_es', expression(verificationFailedText, 'cargo', 'ended', 'true')),
421
+ { id: 'vf_say', type: 'SAY', value: '{{vf_text}}', value_es: '{{vf_text_es}}', outcome: "{{ended && ended.recovery && ended.recovery.mode === 'hold' ? ended.outcome : 'unresolved'}}", reason: "{{ended && ended.recovery && ended.recovery.mode === 'hold' ? ended.reason : 'verification_failed'}}" },
422
+ { id: 'vf_failed_flow', type: 'CASE', branches: { "condition: !(ended && ended.recovery && ended.recovery.mode === 'hold') && !!(cargo.authOptions && cargo.authOptions.failed_flow)": call('vf_run_failed_flow', '{{cargo.authOptions.failed_flow}}', {}), default: set('vf_failed_flow_skip', 'noop', 'true') } },
423
+ set('vf_agent', 'vf_agent', expression(verificationFailedText, 'cargo', 'ended', 'false', "'agent'")), set('vf_agent_es', 'vf_agent_es', expression(verificationFailedText, 'cargo', 'ended', 'true', "'agent'")),
424
+ { id: 'vf_offer_agent', type: 'CASE', branches: { 'condition: !!vf_agent': { id: 'vf_agent_say', type: 'SAY', value: '{{vf_agent}}', value_es: '{{vf_agent_es}}' }, default: set('vf_agent_skip', 'noop', 'true') } },
425
+ end('vf_end')]
426
+ });
427
+ export const FLOWS = [taskGate, resume, service, rawTransport, contact, declined, splitFailed, rejected, auth, code, validateCode, emailToo, retry, email, failed];
@@ -1 +1 @@
1
- export const JSFE_RUNTIME_FINGERPRINT = "sha256:b277b5861474d6a664568a7ba90791e721acc952575b4182d58518de7f568e99";
1
+ export const JSFE_RUNTIME_FINGERPRINT = "sha256:a7ebbdf90eec8ba02813fa2f92b0c93f1282dfd46be3ade0fc9b789d96663198";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsfe",
3
- "version": "1.17.1",
3
+ "version": "1.18.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",