jsfe 1.10.9 → 1.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/dist/forms/flows.d.ts.map +1 -1
- package/dist/forms/flows.js +46 -10
- package/dist/forms/identity-flows.js +4 -4
- package/dist/helpers/answer.d.ts +5 -0
- package/dist/helpers/answer.d.ts.map +1 -1
- package/dist/helpers/answer.js +21 -2
- package/dist/helpers/contextual-response.d.ts.map +1 -1
- package/dist/helpers/contextual-response.js +3 -1
- package/dist/helpers/index.d.ts +3 -1
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/index.js +3 -3
- package/dist/host/index.d.ts +1 -0
- package/dist/host/index.d.ts.map +1 -1
- package/dist/host/index.js +1 -0
- package/dist/host/reply-rank.d.ts +66 -0
- package/dist/host/reply-rank.d.ts.map +1 -0
- package/dist/host/reply-rank.js +126 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +140 -148
- package/dist/runtime-identity.js +1 -1
- package/docs/copilot-reference.md +3 -3
- package/docs/development-release.md +35 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -357,6 +357,23 @@ and the decoders (`decodeFlowLlm`, `decodeFlowSystemOne`, `decodeParametersLlm`,
|
|
|
357
357
|
a stage-2 failure starts the flow without its parameters (it asks for them). The default, `'single-call'`,
|
|
358
358
|
is the original single call.
|
|
359
359
|
|
|
360
|
+
**The reply ranker (contextual DISPATCH, `onResponse: "continue"`):** when a flow's question gets a reply its
|
|
361
|
+
deterministic reader cannot read, jsfe asks what the reply does with the step's `responseContext` (the question):
|
|
362
|
+
`answers`, `abort` or `new_request`. ONE aiCallback call whose 4th argument is `task: 'rank_reply'` with both
|
|
363
|
+
executors' instructions (`llm`, `systemOne`); the host replies `{ "option": … }`, plus `"fallback": true` when the
|
|
364
|
+
option is `answers` only because nothing cleared the cut (System One). `jsfe/host` exports
|
|
365
|
+
`buildReplyRank`, `decodeReplyRankLlm` and `decodeReplyRankSystemOne` (the highest option at or above 0.5, else
|
|
366
|
+
`answers`). `answers` continues the flow, after extracting the step's `responseParameters` with a stage-2 request
|
|
367
|
+
when it declares them. A confident `answers` (no `fallback`) is never lost: when the declaration is one parameter marked
|
|
368
|
+
`verbatim: true` (an open string whose words are the value, such as a reason) and extraction finds nothing or fails,
|
|
369
|
+
the field is the reply as said. Unmarked fields (a product, a city) always need a real extraction. After a
|
|
370
|
+
`fallback` answer extraction is the judge, and ReadFormResponse asks the question again when it finds nothing; `abort` is a RETURN: every flow ends and the reply is system message `cmd_request_aborted`
|
|
371
|
+
("The process has been cancelled at your request - how can I help you?"),
|
|
372
|
+
outcome `user_aborted`, no recovery stamp;
|
|
373
|
+
`new_request` ends the flows and routes the input through intent detection. Only a new request is routed: after
|
|
374
|
+
`answers` a later ordinary DISPATCH in the same turn hands the host the turn without detection. A host that does not know the task answers the `llm`
|
|
375
|
+
instructions with its text model.
|
|
376
|
+
|
|
360
377
|
**Host start (`ContextEntry.start`):** `{ role: 'user', content, start: { flow, arguments } }` starts that
|
|
361
378
|
flow (any flow, by id or name) with those arguments on this turn, without asking the AI. Only the host sets it;
|
|
362
379
|
it is never derived from what the user typed.
|
|
@@ -373,7 +390,7 @@ const engine = new WorkflowEngine(logger, aiCallback, flows, tools,
|
|
|
373
390
|
{ ...STANDARD_FUNCTIONS, sendSMSOTP, sendEmail /* the host's own, credentialed functions */ });
|
|
374
391
|
```
|
|
375
392
|
|
|
376
|
-
- Reading answers: `matchesChoice`, `pickChoice`, `isAnswer`, `isAgentRequest`, `isOtherRequest`,
|
|
393
|
+
- Reading answers: `matchesChoice`, `pickChoice`, `isAnswer`, `isAgentRequest`, `isExitRequest`, `isOtherRequest`,
|
|
377
394
|
`isSpelledFragment`, `isAmountAnswer`, `amountInAnswer`, `readEmail`, `readPhone`,
|
|
378
395
|
`readInternationalPhone`, `readIdentifier`, `normalizeAndFindCapture`, and `matchInputs` (which of a
|
|
379
396
|
form's inputs an answer fills; ties between inputs of the same form are returned for the flow to ask).
|
|
@@ -1142,7 +1159,7 @@ their original workflow seamlessly.
|
|
|
1142
1159
|
### Flow Execution Modes
|
|
1143
1160
|
- ✅ **Linear Flow Execution** - Sequential step processing
|
|
1144
1161
|
- ✅ **Sub-Flow Calls** - Nested workflow execution
|
|
1145
|
-
- ✅ **Flow Interruption** - Suspend current flow for new task
|
|
1162
|
+
- ✅ **Flow Interruption** - Suspend current flow for new task: in a flow with `interruptable: true`, each reply goes through the same intent detection as a new request (two-stage when configured); a detected primary starts on a new stack with its extracted arguments
|
|
1146
1163
|
- ✅ **Flow Resumption** - Return to previously suspended flows
|
|
1147
1164
|
- ✅ **Flow Replacement** - Replace current flow with new flow
|
|
1148
1165
|
- ✅ **Flow Reboot** - Nuclear option: clear all flows and restart (SAY output accumulated before the reboot is kept and delivered with the new flow's output; `discardSay: true` drops it)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../../src/forms/flows.js"],"names":[],"mappings":"AA4HA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4DC;AACD;;;GAGG;AACH;;;;;;EAGC;
|
|
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"}
|
package/dist/forms/flows.js
CHANGED
|
@@ -241,7 +241,7 @@ const collectInput = {
|
|
|
241
241
|
id: 'collect-input', name: 'CollectInput', version: '1.0.0', scope: 'isolated',
|
|
242
242
|
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).",
|
|
243
243
|
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 })),
|
|
244
|
-
variables: vars({ ci_partial: '', ci_exhaustion: null, ci_d: {}, ci_pre: '', ci_val: '', ci_routed: '', ci_names: [], ci_chain: [], ci_spec: null, ci_choices: [], ci_q: '', ci_q_es: '', ci_missed: 0, ci_max: 3, ci_misses: 0, ci_miss_limit: 2, ci_must: false, ci_m: null, ci_qkey: '', ci_skip_gate: '', ci_words: 0, ci_digits: null, ci_amount: false, ci_as_spoken: false, ci_reader: {}, ci_pii: false, ci_ctx: '', ci_ctx_es: '' }),
|
|
244
|
+
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: '' }),
|
|
245
245
|
steps: [
|
|
246
246
|
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; })()"),
|
|
247
247
|
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]"),
|
|
@@ -302,12 +302,13 @@ const collectInput = {
|
|
|
302
302
|
SET('ci_skip_gate', 'ci_skip_gate', "!provides || !provides.gate || [].concat(provides.gate.verified || []).every(function(p){return !!formPath({cargo:cargo},p);}) ? 'true' : ''"),
|
|
303
303
|
WHEN('ci_explicit_question', 'ci_m.questions.length > 0', FLOW('ci_explicit_provide', 'provide-data', 'call', { form: '{{form}}', provides: '{{provides}}', asked: '{{ci_qkey}}', asked_keys: '{{ci_m.questions}}', no_menu: 'true', no_gate: '{{ci_skip_gate}}' })),
|
|
304
304
|
WHEN('ci_explicit_question_done', 'ci_m.questions.length > 0', END('ci_explicit_question_end')),
|
|
305
|
+
SET('ci_reply_reader', 'ci_reply_reader', "ci_choices.length ? 'read-form-choice' : 'read-form-reply'"),
|
|
305
306
|
{ id: 'ci_route', type: 'CASE', branches: {
|
|
306
307
|
'condition: ci_m.ambiguous.length > 0': FLOW('ci_ambiguous', 'disambiguate-input', 'call', { readings: '{{ci_m.ambiguous}}', hits: '{{ci_m.hits}}', remaining: '{{ci_m.remaining}}' }),
|
|
307
308
|
'condition: ci_m.hits.length > 0': FLOW('ci_accept', 'collect-input-accept', 'call', { hits: '{{ci_m.hits}}' }),
|
|
308
309
|
"condition: ci_m.questions.length > 0": END('ci_question_handled'),
|
|
309
310
|
"condition: ci_qkey && ci_qkey.indexOf('__') !== 0": FLOW('ci_question_asked', 'provide-data', 'call', { form: '{{form}}', provides: '{{provides}}', asked: '{{ci_qkey}}', no_menu: 'true', no_gate: '{{ci_skip_gate}}' }),
|
|
310
|
-
"condition: !ci_must && (ci_words >= 3 || ci_spec.profile.kind === 'amount' && ci_words > 0)":
|
|
311
|
+
"condition: !ci_must && (ci_words >= 3 || ci_spec.profile.kind === 'amount' && ci_words > 0)": FLOW('ci_elsewhere', '{{ci_reply_reader}}', 'call', { form: '{{form}}', input: '{{current}}', question: '{{ci_q}}', question_es: '{{ci_q_es}}', choices: '{{ci_choices}}' }),
|
|
311
312
|
default: SIDE('ci_miss', "(missFormInput(cargo,form,current),true)")
|
|
312
313
|
} },
|
|
313
314
|
],
|
|
@@ -344,9 +345,9 @@ const validateBatch = {
|
|
|
344
345
|
SIDE('vb_reset', "(cargo.inputCheck = vb_h.invalid ? { ok: false } : (vb_h.validator && !vb_known ? null : { ok: true, value: vb_h.value }), true)"),
|
|
345
346
|
SET('vb_response', 'vb_response', "(function(){var state=cargo.forms[vb_h.form],spec=state && state.specs.flatMap(function(s){return s.any_of || [s];}).find(function(s){return s.name===vb_h.name;});return spec && spec.response || null;})()"),
|
|
346
347
|
WHEN('vb_contextual_needed', "vb_ready === true && vb_h.current && vb_response && !vb_known && !vb_h.invalid && vb_h.origin !== 'activation' && !vb_h.fromPending", FLOW('vb_read_response', 'read-form-response', 'call', { form: '{{vb_h.form}}', input: '{{vb_h.name}}' })),
|
|
347
|
-
WHEN('vb_validator', 'vb_ready === true && vb_h.validator && !vb_known && !vb_h.invalid', FLOW('vb_validate', '{{vb_h.validator}}', 'call', { form: '{{vb_h.form}}', input: '{{vb_h.name}}', value: '{{vb_h.value}}', values: '{{vb_values}}', reading: '{{vb_h}}' })),
|
|
348
|
+
WHEN('vb_validator', 'vb_ready === true && vb_h.validator && !vb_known && !vb_h.invalid && !(cargo.inputCheck && cargo.inputCheck.ok === false)', FLOW('vb_validate', '{{vb_h.validator}}', 'call', { form: '{{vb_h.form}}', input: '{{vb_h.name}}', value: '{{vb_h.value}}', values: '{{vb_values}}', reading: '{{vb_h}}' })),
|
|
348
349
|
WHEN('vb_refused', 'vb_h.refused === true || vb_h.incompleteMoney === true', SIDE('vb_withhold_refusal', "(cargo.inputCheck=Object.assign({},cargo.inputCheck || {},{ok:false}),true)")),
|
|
349
|
-
SET('vb_candidate', 'vb_candidate', "Object.assign({},vb_h,{ok:!!(cargo.inputCheck && cargo.inputCheck.ok === true),value:cargo.inputCheck && cargo.inputCheck.value !== undefined && cargo.inputCheck.value !== null ? cargo.inputCheck.value : vb_h.value,message:cargo.inputCheck && cargo.inputCheck.message,message_es:cargo.inputCheck && cargo.inputCheck.message_es})"),
|
|
350
|
+
SET('vb_candidate', 'vb_candidate', "Object.assign({},vb_h,{ok:!!(cargo.inputCheck && cargo.inputCheck.ok === true),value:cargo.inputCheck && cargo.inputCheck.value !== undefined && cargo.inputCheck.value !== null ? cargo.inputCheck.value : vb_h.value,message:cargo.inputCheck && cargo.inputCheck.message,message_es:cargo.inputCheck && cargo.inputCheck.message_es,quiet:!!(cargo.inputCheck && cargo.inputCheck.quiet)})"),
|
|
350
351
|
SET('vb_proposal_policy', 'vb_proposal', 'vb_relevant === true && vb_ready === true && vb_candidate.ok && !vb_known ? formProposalPolicy(cargo,vb_candidate,cargo.inputCheck,accepted) : null'),
|
|
351
352
|
SET('vb_proposal_values', 'vb_proposal_values', 'proposedFormValues(cargo,vb_h.form,accepted.concat([vb_candidate]))'),
|
|
352
353
|
SIDE('vb_proposal_reset', '(cargo.proposalCheck=undefined,true)'),
|
|
@@ -365,7 +366,7 @@ const requestCancelled = {
|
|
|
365
366
|
};
|
|
366
367
|
// Open-ended answers use the same contextual intent/extraction path as other DISPATCH replies.
|
|
367
368
|
const readFormResponse = {
|
|
368
|
-
id: 'read-form-response', name: 'ReadFormResponse', version: '1.0.0', scope: 'isolated', description: 'Read the requested field with contextual DISPATCH
|
|
369
|
+
id: 'read-form-response', name: 'ReadFormResponse', version: '1.0.0', scope: 'isolated', description: 'Read the requested field with contextual DISPATCH: an answer continues, an answer with nothing usable is asked again, a withdrawal or a new request leaves with saved form progress.',
|
|
369
370
|
parameters: [{ name: 'form', type: 'string' }, { name: 'input', type: 'string' }],
|
|
370
371
|
variables: vars({ group: null, spec: null, question: '', reply: null }),
|
|
371
372
|
steps: [SET('response_group', 'group', "cargo.forms[form].specs.find(function(s){return s.name===input || s.any_of && s.any_of.some(function(member){return member.name===input;});})"),
|
|
@@ -373,8 +374,42 @@ const readFormResponse = {
|
|
|
373
374
|
{ ...SET('response_declared', 'noop', '!!spec && !!validateFormResponse(spec.response)'), requireTrue: true },
|
|
374
375
|
SET('response_question', 'question', "formInputPrompt(cargo,form,input,Object.assign({},group,spec,{question:spec.question || group.question,question_es:spec.question_es || group.question_es,question_from:spec.question_from || group.question_from}),cargo.forms[form].data || {},language)"),
|
|
375
376
|
SET('response_clear', 'reply', 'null'),
|
|
376
|
-
{ ...yieldDispatch('response_dispatch', 'form', "'input_not_matched'"), onResponse: 'continue', responseContext: 'Current question: {{question}}. Requested answer: {{spec.response.description}}.
|
|
377
|
-
|
|
377
|
+
{ ...yieldDispatch('response_dispatch', 'form', "'input_not_matched'"), onResponse: 'continue', responseContext: 'Current question: {{question}}. Requested answer: {{spec.response.description}}.', responseParameters: [{ name: 'answer', type: 'string', description: '{{spec.response.description}}', verbatim: true }], responseVariable: 'reply' },
|
|
378
|
+
// Nothing usable (only after a default `answers`: a confident one carries the reply as said): the reading is
|
|
379
|
+
// rejected, so the form asks the question again (the question alone: it was not a wrong answer). The answer never
|
|
380
|
+
// goes to the host.
|
|
381
|
+
WHEN('response_missing', "!reply || typeof reply.answer !== 'string' || !reply.answer.trim()", SIDE('response_rejected', '(cargo.inputCheck={ok:false,quiet:true},true)'))]
|
|
382
|
+
};
|
|
383
|
+
// An answer the field's reader could not read goes to the reply ranker, never straight to the host (owner 2026-10-26: a form
|
|
384
|
+
// gives up only for a real new intent). `answers`: a choice field takes the offered key the reply picks, else the
|
|
385
|
+
// question is asked again ("Sorry, I didn't catch that."), counted as an attempt so max_attempts still bounds it;
|
|
386
|
+
// `abort` ends every flow; `new_request` goes to intent detection.
|
|
387
|
+
const rankedMiss = (id) => SIDE(id, "(cargo.forms[form].attempts=cargo.forms[form].attempts || {},cargo.forms[form].attempts[input]=Number(cargo.forms[form].attempts[input] || 0)+1,cargo.forms[form].feedback=cargo.forms[form].feedback || {},cargo.forms[form].feedback[input]='miss',true)");
|
|
388
|
+
const readFormReply = {
|
|
389
|
+
id: 'read-form-reply', name: 'ReadFormReply', version: '1.0.0', scope: 'isolated', description: 'Rank an unreadable answer against its question: an answer is asked again, a withdrawal or a new request leaves with saved form progress.',
|
|
390
|
+
parameters: [{ name: 'form', type: 'string' }, { name: 'input', type: 'string' }, { name: 'question', type: 'string' }, { name: 'question_es', type: 'string' }],
|
|
391
|
+
variables: vars({ question: '', question_es: '', asked: '' }),
|
|
392
|
+
steps: [SET('reply_asked', 'asked', "language === 'es' && question_es ? question_es : question"),
|
|
393
|
+
{ ...yieldDispatch('reply_dispatch', 'form', "'input_not_matched'"), onResponse: 'continue', responseContext: 'Current question: {{asked}}' },
|
|
394
|
+
rankedMiss('reply_miss')]
|
|
395
|
+
};
|
|
396
|
+
const readFormChoice = {
|
|
397
|
+
id: 'read-form-choice', name: 'ReadFormChoice', version: '1.0.0', scope: 'isolated', description: 'Rank an unreadable answer to a choice question and take the offered option it picks; otherwise ask again. A withdrawal or a new request leaves with saved form progress.',
|
|
398
|
+
parameters: [{ name: 'form', type: 'string' }, { name: 'input', type: 'string' }, { name: 'question', type: 'string' }, { name: 'question_es', type: 'string' }, { name: 'choices', type: 'array' }],
|
|
399
|
+
variables: vars({ question: '', question_es: '', choices: [], asked: '', options: '', reply: null, picked: '', chain: null, reading: null }),
|
|
400
|
+
steps: [SET('choice_asked', 'asked', "language === 'es' && question_es ? question_es : question"),
|
|
401
|
+
SET('choice_options', 'options', "(choices || []).map(function(c){return String(c.key)+' ('+String(c.label || (Array.isArray(c.phrases) && c.phrases.filter(function(p){return String(p)!==String(c.key);})[0]) || c.key)+')';}).join(', ')"),
|
|
402
|
+
{ ...yieldDispatch('choice_dispatch', 'form', "'input_not_matched'"), onResponse: 'continue', responseContext: 'Current question: {{asked}}', responseParameters: [{ name: 'key', type: 'string', description: 'The key of the offered option the reply picks or says it already chose, one of: {{options}}. Leave it empty when the reply picks none of them.' }], responseVariable: 'reply' },
|
|
403
|
+
SET('choice_picked', 'picked', "(function(){var k=String(reply && reply.key || '').trim().toLowerCase();var c=(choices || []).filter(function(c){return String(c.key).toLowerCase()===k;})[0];return c ? String(c.key) : '';})()"),
|
|
404
|
+
WHEN('choice_none', '!picked', rankedMiss('choice_miss')),
|
|
405
|
+
WHEN('choice_none_end', '!picked', END('choice_none_done')),
|
|
406
|
+
SIDE('choice_take', "(cargo.choice=picked,cargo.choiceText=picked,true)"),
|
|
407
|
+
SET('choice_chain', 'chain', 'formInputChain(cargo.forms, form, cargo)'),
|
|
408
|
+
SET('choice_read', 'reading', "enrichFormMatches(readFormAnswer(picked, chain, { form: form, name: input }, picked, []), chain, form, [input], input)"),
|
|
409
|
+
WHEN('choice_unread', '!reading.hits.length', rankedMiss('choice_unread_miss')),
|
|
410
|
+
WHEN('choice_unread_end', '!reading.hits.length', END('choice_unread_done')),
|
|
411
|
+
SIDE('choice_clear', 'clearFormMisses(cargo,form,input)'),
|
|
412
|
+
FLOW('choice_accept', 'collect-input-accept', 'call', { hits: '{{reading.hits}}' })]
|
|
378
413
|
};
|
|
379
414
|
const collectContextualAnswer = formFlow({ id: 'collect-contextual-answer', name: 'CollectContextualAnswer', description: 'Collect one open-ended answer using contextual DISPATCH and preserve the caller wording.',
|
|
380
415
|
parameters: [...['question', 'question_es', 'description', 'context', 'context_es'].map(name => ({ name, type: 'string' })), { name: 'choices', type: 'array' }, { name: 'digits', type: 'object' }],
|
|
@@ -415,8 +450,8 @@ const acceptedEffects = {
|
|
|
415
450
|
WHEN('ie_none', '!ca_h', END('ie_end')),
|
|
416
451
|
SET('ie_rest', 'ca_rest', 'readings.slice(1)'),
|
|
417
452
|
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)")),
|
|
418
|
-
SET('ie_say_en', 'ca_ack', "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 updated your ' + ca_h.label + '.') : (ca_h.outcome === 'taken' || ca_h.outcome === 'pending') && ca_h.origin !== 'activation' ? ('Got it, your ' + ca_h.label + '.') : ''"),
|
|
419
|
-
SET('ie_say_es', 'ca_ack_es', "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, actualicé su ' + ca_h.label_es + '.') : (ca_h.outcome === 'taken' || ca_h.outcome === 'pending') && ca_h.origin !== 'activation' ? ('Entendido, su ' + ca_h.label_es + '.') : ''"),
|
|
453
|
+
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 updated your ' + ca_h.label + '.') : (ca_h.outcome === 'taken' || ca_h.outcome === 'pending') && ca_h.origin !== 'activation' ? ('Got it, your ' + ca_h.label + '.') : ''"),
|
|
454
|
+
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, actualicé su ' + ca_h.label_es + '.') : (ca_h.outcome === 'taken' || ca_h.outcome === 'pending') && ca_h.origin !== 'activation' ? ('Entendido, su ' + ca_h.label_es + '.') : ''"),
|
|
420
455
|
WHEN('ie_say', 'ca_ack', SAY('ie_ack', '{{ca_ack}}', '{{ca_ack_es}}')),
|
|
421
456
|
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}}' })),
|
|
422
457
|
WHEN('ie_more', 'ca_rest.length > 0', FLOW('ie_next', 'input-accepted-effects', 'replace', { readings: '{{ca_rest}}' })),
|
|
@@ -791,7 +826,7 @@ const resumeForm = { id: 'resume-form', name: 'ResumeForm', version: '1.0.0', sc
|
|
|
791
826
|
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 || ''"),
|
|
792
827
|
FLOW('resume_owned_form', '{{target}}', 'replace', { form_instance: '{{form}}', parent_form: '{{parent}}', result_input: '{{slot}}' })
|
|
793
828
|
] };
|
|
794
|
-
const LIBRARY_FLOWS = [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];
|
|
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];
|
|
795
830
|
const LIBRARY_CONTRACTS = {
|
|
796
831
|
'confirm-input-proposal': { role: 'collector', requires: [INPUT_PROPOSALS_REQUIREMENT] }, 'confirm-input-proposal-declined': { role: 'worker', requires: [INPUT_PROPOSALS_REQUIREMENT] },
|
|
797
832
|
'resume-form': { role: 'worker' },
|
|
@@ -800,6 +835,7 @@ const LIBRARY_CONTRACTS = {
|
|
|
800
835
|
'prepare-input': { role: 'worker' }, 'run-form': { role: 'worker' }, 'collect-input': { role: 'worker' },
|
|
801
836
|
'request-cancelled': { role: 'worker' },
|
|
802
837
|
'read-form-response': { role: 'worker', requires: [CONTEXTUAL_RESPONSES_REQUIREMENT] },
|
|
838
|
+
'read-form-reply': { role: 'worker' }, 'read-form-choice': { role: 'worker', requires: [CONTEXTUAL_RESPONSES_REQUIREMENT] },
|
|
803
839
|
'collect-contextual-answer': { role: 'collector', requires: [CONTEXTUAL_RESPONSES_REQUIREMENT] },
|
|
804
840
|
'validate-input-batch': { role: 'worker', result: { path: 'cargo.inputBatch', type: 'object' } },
|
|
805
841
|
'collect-input-accept': { role: 'worker' }, 'input-accepted-effects': { role: 'worker' },
|
|
@@ -111,7 +111,7 @@ const contact = worker('get-cell-or-email', 'GetCellOrEmail', 'Identify a suppli
|
|
|
111
111
|
when('identity_contact_split_overshoot', "contact_partial && contact_digits.length > (contact_options.account_shape || []).reduce(function(max,s){return Math.max(max,Math.max.apply(null,s.lengths || [0]));},0)", call('identity_contact_restart_split', 'contact-split-failed', { contact_options: '{{contact_options}}' }, 'replace')),
|
|
112
112
|
set('identity_contact_identifier', 'contact_id', "contact_options.account_pattern ? readIdentifier(contact_text,contact_options.account_pattern) : null"),
|
|
113
113
|
set('identity_contact_phone', 'contact_phone', "readPhone(contact_text) || (validatePhone(contact_digits) ? contact_digits : '')"), set('identity_contact_email', 'contact_mail', 'readEmail(contact_text)'),
|
|
114
|
-
set('identity_contact_classify', 'contact_kind', "contact_id && contact_id.kind === 'account' ? 'account' : contact_phone ? 'phone' : contact_mail ? 'email' : contact_options.account_pattern && !contact_digits && matchesChoice(contact_text,contact_names_account) ? 'names_account' : readInternationalPhone(contact_text) ? 'international' : isAgentRequest(contact_text) ? 'agent' :
|
|
114
|
+
set('identity_contact_classify', 'contact_kind', "contact_id && contact_id.kind === 'account' ? 'account' : contact_phone ? 'phone' : contact_mail ? 'email' : contact_options.account_pattern && !contact_digits && matchesChoice(contact_text,contact_names_account) ? 'names_account' : readInternationalPhone(contact_text) ? 'international' : isAgentRequest(contact_text) ? 'agent' : isExitRequest(contact_text) ? 'exit' : contact_allowed && /^(?:no|nope|nah)\\b/i.test(contact_text.trim()) && !/[0-9@]/.test(contact_text) ? 'decline' : contact_allowed && cargo.serviceAnswer && cargo.serviceAnswer.values.caller_decision === 'yes' ? 'caller' : contact_digits ? 'invalid_phone' : contact_text.trim().split(/\\s+/).length>=3 && !/[0-9@]/.test(contact_text) ? 'unmapped' : 'invalid'"),
|
|
115
115
|
set('identity_contact_named', 'contact_named_options', "Object.assign({},contact_options,{fromMenu:false,intro:'Sure, your account number.',intro_es:'Claro, su número de cuenta.'})"),
|
|
116
116
|
set('identity_contact_error', 'contact_error', expression(contactError, 'contact_kind', 'contact_options')), set('identity_contact_error_es', 'contact_error_es', expression(contactError, 'contact_kind', 'contact_options', 'true')),
|
|
117
117
|
cases('identity_contact_route', {
|
|
@@ -212,7 +212,7 @@ const validateCode = worker('validate-otp-code', 'ValidateOtpCode', 'Validate a
|
|
|
212
212
|
cases('otp_response', {
|
|
213
213
|
'condition: normalized_otp_code.length === 6': { id: 'otp_check_tool', type: 'CALL-TOOL', tool: 'validate-otp', variable: 'otp_validation_result', args: { otp: '{{normalized_otp_code}}', container: '{{cargo}}' }, onFail: call('otp_check_failed', 'get-and-validate-otp-code', undefined, 'replace') },
|
|
214
214
|
'condition: wants_other_account': call('otp_change_account', 'authenticate-user', { cell_number: '', email: '', retrying: true, declined_caller: true }, 'replace'),
|
|
215
|
-
"condition:
|
|
215
|
+
"condition: isExitRequest(user_choice)": call('otp_exit', '{{otp_cancel}}', undefined, 'reboot'),
|
|
216
216
|
"condition: isAnswer(user_choice,'agent')": call('otp_agent', 'live-agent-requested', undefined, 'reboot'),
|
|
217
217
|
"condition: isAnswer(user_choice,'text')": call('otp_text', 'switch-to-text', undefined, 'reboot'),
|
|
218
218
|
'condition: matchesChoice(user_choice,otp_phrases.resend)': call('otp_resend', 'resend-otp-code', { resend_reason: 'requested' }),
|
|
@@ -261,7 +261,7 @@ const retry = worker('generic-retry-with-options', 'GenericRetryWithOptions', 'C
|
|
|
261
261
|
set('retry_latest_answer', 'user_choice', 'String(cargo.serviceAnswer.raw || "")'), set('retry_capture', 'smart_capture_result', 'capture_patterns && capture_patterns.length ? normalizeAndFindCapture(user_choice,capture_patterns) : null'),
|
|
262
262
|
cases('retry_capture_route', { 'condition: smart_capture_result && retry_call_type === "replace"': call('retry_captured_replace', '{{retry_flow}}', { cell_number: '', email: '', '{{smart_capture_result.variable}}': '{{smart_capture_result.value}}', retry_flow: '{{retry_flow}}', cancel_flow: '{{cancel_flow}}', retrying: true, contact_options: null, parent_form: '{{parent_form}}' }, 'replace'), 'condition: smart_capture_result': call('retry_captured_reboot', '{{retry_flow}}', { '{{smart_capture_result.variable}}': '{{smart_capture_result.value}}', retry_flow: '{{retry_flow}}', cancel_flow: '{{cancel_flow}}', retrying: true, contact_options: null, parent_form: '{{parent_form}}' }, 'reboot'), default: effect('retry_no_capture', 'true') }),
|
|
263
263
|
set('retry_typed_code', 'normalized_otp_code', "allow_code_entry && user_choice.replace(/\\D/g,'').length === 6 ? user_choice.replace(/\\D/g,'') : ''"), when('retry_validate_code', '!!normalized_otp_code', call('retry_code_validate', 'validate-otp-code')),
|
|
264
|
-
cases('retry_decision', { 'condition: normalized_otp_code && cargo.otpVerified === true': end('retry_verified'), "condition: (user_choice.trim() === '1' || isAnswer(user_choice,'yes',['phone','email','cell','mobile','teléfono','telefono','celular','móvil','movil','correo electrónico','correo'])) && retry_call_type === 'replace'": call('retry_yes_replace', '{{retry_flow}}', { retrying: true, cell_number: '', email: '', contact_options: null, parent_form: '{{parent_form}}' }, 'replace'), "condition: user_choice.trim() === '1' || isAnswer(user_choice,'yes',['phone','email','cell','mobile','teléfono','telefono','celular','móvil','movil','correo electrónico','correo'])": call('retry_yes_reboot', '{{retry_flow}}', { retrying: true, contact_options: null, parent_form: '{{parent_form}}' }, 'reboot'), "condition: user_choice.trim() === '9' || isAnswer(user_choice,'text')": call('retry_text', 'switch-to-text', undefined, 'reboot'), "condition: isAgentRequest(user_choice)": call('retry_agent', 'live-agent-requested', undefined, 'reboot'), "condition: !user_choice.trim() || /^\\d$/.test(user_choice.trim()) ||
|
|
264
|
+
cases('retry_decision', { 'condition: normalized_otp_code && cargo.otpVerified === true': end('retry_verified'), "condition: (user_choice.trim() === '1' || isAnswer(user_choice,'yes',['phone','email','cell','mobile','teléfono','telefono','celular','móvil','movil','correo electrónico','correo'])) && retry_call_type === 'replace'": call('retry_yes_replace', '{{retry_flow}}', { retrying: true, cell_number: '', email: '', contact_options: null, parent_form: '{{parent_form}}' }, 'replace'), "condition: user_choice.trim() === '1' || isAnswer(user_choice,'yes',['phone','email','cell','mobile','teléfono','telefono','celular','móvil','movil','correo electrónico','correo'])": call('retry_yes_reboot', '{{retry_flow}}', { retrying: true, contact_options: null, parent_form: '{{parent_form}}' }, 'reboot'), "condition: user_choice.trim() === '9' || isAnswer(user_choice,'text')": call('retry_text', 'switch-to-text', undefined, 'reboot'), "condition: isAgentRequest(user_choice)": call('retry_agent', 'live-agent-requested', undefined, 'reboot'), "condition: !user_choice.trim() || /^\\d$/.test(user_choice.trim()) || isExitRequest(user_choice)": call('retry_cancel', '{{cancel_flow}}', undefined, 'reboot'), default: { id: 'retry_dispatch', type: 'DISPATCH', outcome: '{{caller_outcome}}', reason: '{{caller_reason}}' } })
|
|
265
265
|
] });
|
|
266
266
|
const EMAIL_CHOICES = { ok: ['1', 'ok', 'okay', 'yes', 'send', 'send it', 'si', 'sí', 'enviar', 'envíalo', 'correcto'], another: ['otro correo', 'otro correo electrónico', 'otro correo electronico', 'otro email', 'otra dirección', 'otra direccion', 'a otro correo', 'another email', 'a different email', 'different email', 'other email', 'another address', 'a different address'] };
|
|
267
267
|
export function emailReadback(address, voice, es = false) {
|
|
@@ -277,7 +277,7 @@ const email = worker('ask-email', 'AskEmail', 'Collect an email through shared F
|
|
|
277
277
|
set('email_question_es', 'email_question_es', "email_state.why === 'more' ? 'Entendido. Continúe.' : (email_state.attempt>0 ? email_state.why === 'retry' ? 'De acuerdo, intentemos de nuevo. ' : 'Eso no parece un correo electrónico. ' : '')+email_state.question_es"),
|
|
278
278
|
call('email_collect', 'collect-service-answer', { profiles: '{{email_profiles}}', question: '{{email_question}}', question_es: '{{email_question_es}}', keypad: { min: 1, max: 1 }, parent_form: '{{email_state.parent_form}}', context: 'email', context_es: 'correo' }),
|
|
279
279
|
set('email_raw', 'email_raw', 'String(cargo.serviceAnswer.raw || "")'),
|
|
280
|
-
cases('email_command', { "condition: isAgentRequest(email_raw)": call('email_agent', 'live-agent-requested', undefined, 'reboot'), "condition:
|
|
280
|
+
cases('email_command', { "condition: isAgentRequest(email_raw)": call('email_agent', 'live-agent-requested', undefined, 'reboot'), "condition: isExitRequest(email_raw)": call('email_exit', 'contact-support', { support_context: 'email', support_context_es: 'correo' }, 'reboot'), default: effect('email_is_answer', 'true') }),
|
|
281
281
|
set('email_choice', 'email_choice', "pickChoice(email_raw,[{key:'ok',phrases:email_choices.ok},{key:'another',phrases:email_choices.another}],false)"),
|
|
282
282
|
when('email_other_request_clear', "!email_choice && !readEmail(email_raw) && isOtherRequest(email_raw,'email')", effect('email_other_request_draft', "(email_state.draft='',cargo.emailDraft='',cargo.email='',true)")),
|
|
283
283
|
when('email_other_request', "!email_choice && !readEmail(email_raw) && isOtherRequest(email_raw,'email')", { id: 'email_dispatch_request', type: 'DISPATCH' }),
|
package/dist/helpers/answer.d.ts
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
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
|
+
/**
|
|
7
|
+
* Whether the WHOLE answer is an exit word ("exit", "Cancelar.", "salir por favor", "*"), never a sentence that contains
|
|
8
|
+
* one: "Cancelar el pedido" asks for something; it does not withdraw (prod 2026-10-26, at a verification-code prompt).
|
|
9
|
+
*/
|
|
10
|
+
export function isExitRequest(input: any): boolean;
|
|
6
11
|
/** Whether the WHOLE answer asks for a person ("agent", "I want to talk to a representative", "0") — for free-text prompts. */
|
|
7
12
|
export function isAgentRequest(input: any): boolean;
|
|
8
13
|
/** Whether the answer is (part of) a spelled address — letters said one at a time, or a spelled symbol. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../src/helpers/answer.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../src/helpers/answer.js"],"names":[],"mappings":"AAsNA;;;GAGG;AACH,wEAkBC;AAED;;;GAGG;AACH,mDAGC;AAED,+HAA+H;AAC/H,oDAIC;AAcD,2GAA2G;AAC3G,uDAGC;AAOD;;;;;GAKG;AACH,+DAUC;AAKD;;;;GAIG;AACH,oDAEC;AAiBD;;;;;;GAMG;AACH,mDAAmF;AAEnF,kGAAkG;AAClG;;;EAmCC;AAUD;;;;;;;;;GASG;AACH,6EA4GC;;;;;;;;AAjcD,qCAKwE"}
|
package/dist/helpers/answer.js
CHANGED
|
@@ -31,7 +31,7 @@ export const ANSWER_WORDS = {
|
|
|
31
31
|
agent: ['live agent', 'agent', 'agents', 'representative', 'rep', 'human', 'operator', 'manager', 'supervisor', 'someone',
|
|
32
32
|
'somebody', 'real person', 'a person', 'customer service', 'support', 'agente', 'agentes', 'gente', 'representante',
|
|
33
33
|
'humano', 'operador', 'operadora', 'gerente', 'alguien', 'persona', 'servicio al cliente', 'al cliente', 'servicio',
|
|
34
|
-
'recepcionista', 'recepcion', 'recepción'],
|
|
34
|
+
'recepcionista', 'recepcion', 'recepción', 'asesor', 'asesora', 'asesores'],
|
|
35
35
|
exit: ['exit', 'quit', 'abort', 'salir', 'cancel', 'cancelar'],
|
|
36
36
|
text: ['text', 'texto', 'sms', 'message', 'mensaje', 'text me', 'by text', 'por texto', 'mensaje de texto', 'whatsapp',
|
|
37
37
|
'escríbeme', 'escribeme'],
|
|
@@ -60,7 +60,7 @@ const NEGATIONS = ['no', 'not', 'nope', 'nah', 'never', 'dont', 'doesnt', 'didnt
|
|
|
60
60
|
// Phrases that hold a negation word but agree ("no problem", "why not").
|
|
61
61
|
const AGREEING = /\b(no problem|no worries|no hay problema|no te preocupes|why not|por que no|por qué no)\b/g;
|
|
62
62
|
const DEFERRAL = /\b(later|tomorrow|next week|not now|not today|another time|mañana|manana|después|despues|luego|otro día|otro dia|ahora no|hoy no)\b/;
|
|
63
|
-
const AGENT_ONLY = /^(?:(?:i want|i need|i would like|id like|can i|could i|let me|please|quiero|necesito|puedo|por favor)\s+)*(?:(?:to\s+)?(?:talk|speak|hablar|comunicarme)\s+(?:to|with|con)\s+)?(?:a|an|un|una|el|la)?\s*(?:live agent|agent|representative|rep|human|operator|real person|a person|person|someone|somebody|customer service|agente|representante|humano|operador|operadora|persona|alguien|servicio al cliente|agente en vivo|gerente|manager|supervisor)(?:\s+(?:please|por favor))?$/;
|
|
63
|
+
const AGENT_ONLY = /^(?:(?:i want|i need|i would like|id like|can i|could i|let me|please|quiero|necesito|puedo|por favor)\s+)*(?:(?:to\s+)?(?:talk|speak|hablar|comunicarme)\s+(?:to|with|con)\s+)?(?:a|an|un|una|el|la)?\s*(?:live agent|agent|representative|rep|human|operator|real person|a person|person|someone|somebody|customer service|agente|representante|humano|operador|operadora|persona|alguien|servicio al cliente|agente en vivo|gerente|manager|supervisor|asesor|asesora)(?:\s+(?:please|por favor))?$/;
|
|
64
64
|
// Accents are not meaning here (owner 2026-10-16): text callers type "direccion" for "dirección", "si" for "sí". Both
|
|
65
65
|
// sides of every comparison pass through normalize, so a word list matches with or without them; ñ is kept.
|
|
66
66
|
const plainVowels = (s) => s.replace(/[áàâä]/g, 'a').replace(/[éèêë]/g, 'e').replace(/[íìîï]/g, 'i').replace(/[óòôö]/g, 'o').replace(/[úùûü]/g, 'u');
|
|
@@ -196,6 +196,11 @@ function firmKey(b, key) {
|
|
|
196
196
|
// Words that name the key itself when said right before it: "el número 4", "opción 2", "number 4", "the 4" (prod
|
|
197
197
|
// 2026-10-25 18:25: "Número 4" and "El número 4" at a numbered product list were not understood, twice).
|
|
198
198
|
const KEY_NAMES = new Set(['number', 'numero', 'option', 'opcion', 'item', 'choice', 'the', 'el', 'la'].map(normalize));
|
|
199
|
+
// Saying the key was pressed, before it: "ya oprimí el uno", "I already pressed one", "marqué el 2" (prod 2026-10-26:
|
|
200
|
+
// a pressed-key sentence at a sub-account choice went to the guru).
|
|
201
|
+
const PRESS_WORDS = new Set(['ya', 'already', 'i', 'yo', 'ive', 'just', 'pressed', 'press', 'pushed', 'push', 'hit', 'dialed',
|
|
202
|
+
'entered', 'chose', 'picked', 'selected', 'presione', 'presiono', 'presiona', 'oprimi', 'oprime', 'oprimo', 'marque', 'marco',
|
|
203
|
+
'marca', 'pulse', 'pulso', 'toque', 'elegi', 'escogi', 'seleccione', 'le', 'di', 'al', 'a'].map(normalize));
|
|
199
204
|
function keyWithOnlyCourtesy(b, key, yesOk) {
|
|
200
205
|
const tk = String(b).replace(/\./g, ' ').split(' ').filter(Boolean);
|
|
201
206
|
const at = tk.reduce((acc, t, i) => (t === key ? [...acc, i] : acc), []);
|
|
@@ -206,6 +211,12 @@ function keyWithOnlyCourtesy(b, key, yesOk) {
|
|
|
206
211
|
let from = at[0];
|
|
207
212
|
while (from > 0 && at[0] - from < 2 && KEY_NAMES.has(tk[from - 1]))
|
|
208
213
|
from--;
|
|
214
|
+
// a press verb is required for the press words to count ("ya" alone, "a 1", are not a press)
|
|
215
|
+
let press = from;
|
|
216
|
+
while (press > 0 && from - press < 4 && PRESS_WORDS.has(tk[press - 1]))
|
|
217
|
+
press--;
|
|
218
|
+
if (tk.slice(press, from).some((t) => !['ya', 'already', 'i', 'yo', 'ive', 'just', 'le', 'al', 'a'].includes(t)))
|
|
219
|
+
from = press;
|
|
209
220
|
const rest = tk.filter((t, i) => i < from || i > at[0]).join(' ');
|
|
210
221
|
return onlyAffirmative(rest, yesOk ? ANSWER_WORDS.yes : [], false);
|
|
211
222
|
}
|
|
@@ -239,6 +250,14 @@ export function isAnswer(input, kind, extra = []) {
|
|
|
239
250
|
return matchesChoice(b, words);
|
|
240
251
|
}
|
|
241
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Whether the WHOLE answer is an exit word ("exit", "Cancelar.", "salir por favor", "*"), never a sentence that contains
|
|
255
|
+
* one: "Cancelar el pedido" asks for something; it does not withdraw (prod 2026-10-26, at a verification-code prompt).
|
|
256
|
+
*/
|
|
257
|
+
export function isExitRequest(input) {
|
|
258
|
+
const b = normalize(input).replace(/^[.\s]+|[.\s]+$/g, '');
|
|
259
|
+
return b === '*' || /^(exit|quit|salir|abort|cancel|cancelar)( please| por favor)?$/.test(b);
|
|
260
|
+
}
|
|
242
261
|
/** Whether the WHOLE answer asks for a person ("agent", "I want to talk to a representative", "0") — for free-text prompts. */
|
|
243
262
|
export function isAgentRequest(input) {
|
|
244
263
|
// the period normalize() keeps for emails is not part of the answer at its ends ("Operator." on a call, 2026-10-17)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contextual-response.d.ts","sourceRoot":"","sources":["../../src/helpers/contextual-response.js"],"names":[],"mappings":"AAMA,2GAA2G;AAC3G;;;
|
|
1
|
+
{"version":3,"file":"contextual-response.d.ts","sourceRoot":"","sources":["../../src/helpers/contextual-response.js"],"names":[],"mappings":"AAMA,2GAA2G;AAC3G;;;qBAkBC;AACD,+FAIC;AAED,gGAAgG;AAChG,yDAGC;AApCD,4FAA4F;AAC5F,+CAAgD,8BAA8B,CAAC;AACxE,qDAA0C;AAmC1C,kDAA8J"}
|
|
@@ -30,7 +30,9 @@ export function validateContextualResponseStep(step, flow) {
|
|
|
30
30
|
fail('response parameters must be public extraction parameters');
|
|
31
31
|
if (own(p, 'jsonSchema'))
|
|
32
32
|
fail('response parameters support primitive types only; jsonSchema is not supported');
|
|
33
|
-
if (
|
|
33
|
+
if (p.verbatim !== undefined && (p.verbatim !== true || p.type !== undefined && p.type !== 'string' || p.enum !== undefined))
|
|
34
|
+
fail('verbatim is true on an open string parameter only');
|
|
35
|
+
if (Object.keys(p).some(k => !['name', 'description', 'type', 'enum', 'internal', 'verbatim'].includes(k)))
|
|
34
36
|
fail('responseParameters accepts only canonical parameter descriptors');
|
|
35
37
|
}
|
|
36
38
|
try {
|
package/dist/helpers/index.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ export namespace STANDARD_FUNCTIONS {
|
|
|
33
33
|
export { matchesChoice };
|
|
34
34
|
export { isAnswer };
|
|
35
35
|
export { isAgentRequest };
|
|
36
|
+
export { isExitRequest };
|
|
36
37
|
export { isOtherRequest };
|
|
37
38
|
export { isSpelledFragment };
|
|
38
39
|
export { isAmountAnswer };
|
|
@@ -152,6 +153,7 @@ import { ANSWER_WORDS } from './answer.js';
|
|
|
152
153
|
import { CONFIRM_WORDS } from './answer.js';
|
|
153
154
|
import { isAnswer } from './answer.js';
|
|
154
155
|
import { isAgentRequest } from './answer.js';
|
|
156
|
+
import { isExitRequest } from './answer.js';
|
|
155
157
|
import { isOtherRequest } from './answer.js';
|
|
156
158
|
import { isSpelledFragment } from './answer.js';
|
|
157
159
|
import { isAmountAnswer } from './answer.js';
|
|
@@ -238,5 +240,5 @@ import { validatePhone } from './validators.js';
|
|
|
238
240
|
import { validateEmail } from './validators.js';
|
|
239
241
|
import { urlToSpeech } from './url-speech.js';
|
|
240
242
|
import { textWithUrlToSpeech } from './url-speech.js';
|
|
241
|
-
export { continuationSafetyProtocol, cancelFormTask, contextualResponseProtocol, validateFormResponse, hasFormResponse, formProposalPolicy, stageFormProposal, nextFormProposal, formProposalReadings, bindFormProposal, decideFormProposal, commitFormProposals, settleFormProposalValidation, formProposalProtocol, validateFormProposalPolicy, validateFormProposalDescriptor, formProposalDescriptor, clearFormMisses, missFormInput, formMustComplete, formInputPrompt, consumeFormFeedback, formConversationOutcome, readMoneyAmount, isMoneyAmount, moneyIntentAllows, isPastPaymentStatement, isPartialMoneyAnswer, validateMoneyOptions, takeChoiceOffer, matchesChoice, ANSWER_WORDS, CONFIRM_WORDS, isAnswer, isAgentRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice, readEmail, readPhone, readInternationalPhone, readIdentifier, last4, maskEmail, callerTextNumber, textDestination, callerIsInternational, normalizeAndFindCapture, anythingElse, goodbye, sayName, dateToWords, amountToSpeech, validateCardNumber, validateExpiration, consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFormAction, confirmFormRevision, prepareFormAction, actionConfirmationValid, beginFormAction, finishFormAction, actionGateVerified, sourceNeedsLoad, beginSourceRefresh, completeSourceRefresh, pickProviderQuestion, matchInputs, readFormAnswer, readInputProfile, formAcceptanceTrace, resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, formInputChain, openFormInstance, validateFormIncompleteOutcome, formIncompleteOutcome, validateFormExhaustion, exhaustFormInput, formInitialReadings, orderFormReadings, proposedFormValues, applyFormValidation, formReadingDependencies, exclusiveFormReadings, enrichFormMatches, expandFormActivations, settleFormActivations, rememberFormArguments, resetFormInputs, hasFormValue, formPath, formValuesEqual, formReadingReady, formReadingRelevant, formPreparation, queueFormReadings, pendingFormReadings, formWorkLimit, answerResetsFormReplays, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, formParentResume, closeFormInstance, checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech };
|
|
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 };
|
|
242
244
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/helpers/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/helpers/index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2CACyC,iCAAiC;+BAI8D,wBAAwB;2CAHlF,0BAA0B;qCAA1B,0BAA0B;gCAA1B,0BAA0B;mCACsE,qBAAqB;kCAArB,qBAAqB;iCAArB,qBAAqB;qCAArB,qBAAqB;iCAArB,qBAAqB;mCAArB,qBAAqB;oCAArB,qBAAqB;6CAArB,qBAAqB;qCAC/E,wBAAwB;2CAAxB,wBAAwB;+CAAxB,wBAAwB;uCAAxB,wBAAwB;gCACJ,wBAAwB;8BAAxB,wBAAwB;iCAAxB,wBAAwB;gCAAxB,wBAAwB;oCAAxB,wBAAwB;wCAAxB,wBAAwB;gCAmBjC,YAAY;8BAAZ,YAAY;kCAAZ,YAAY;uCAAZ,YAAY;qCAAZ,YAAY;qCAAZ,YAAY;gCAlB3G,mBAAmB;8BAiBrB,qBAAqB;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"}
|
package/dist/helpers/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import { consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFo
|
|
|
23
23
|
import { sourceNeedsLoad, beginSourceRefresh, completeSourceRefresh, pickProviderQuestion } from './form-sources.js';
|
|
24
24
|
import { matchesChoice } from './matches-choice.js';
|
|
25
25
|
import { readMoneyAmount, isMoneyAmount, moneyIntentAllows, isPastPaymentStatement, isPartialMoneyAnswer, validateMoneyOptions } from './money.js';
|
|
26
|
-
import { ANSWER_WORDS, CONFIRM_WORDS, isAnswer, isAgentRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice } from './answer.js';
|
|
26
|
+
import { ANSWER_WORDS, CONFIRM_WORDS, isAnswer, isAgentRequest, isExitRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice } from './answer.js';
|
|
27
27
|
import { readEmail, readPhone, readInternationalPhone, readIdentifier, last4, maskEmail, callerTextNumber, textDestination, callerIsInternational } from './contact.js';
|
|
28
28
|
import { normalizeAndFindCapture } from './capture.js';
|
|
29
29
|
import { anythingElse, goodbye, sayName } from './phrases.js';
|
|
@@ -35,13 +35,13 @@ 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, 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, 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
43
|
clearFormMisses, missFormInput, formMustComplete, formInputPrompt, consumeFormFeedback, formConversationOutcome,
|
|
44
|
-
readMoneyAmount, isMoneyAmount, moneyIntentAllows, isPastPaymentStatement, isPartialMoneyAnswer, validateMoneyOptions, takeChoiceOffer, matchesChoice, isAnswer, isAgentRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice,
|
|
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,
|
|
47
47
|
validateOTP, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech,
|
package/dist/host/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { validateFlowRecovery, flowHandoffInfo, renderFlowRecovery } from './flo
|
|
|
10
10
|
export type { FlowRecovery } from './flow-recovery.js';
|
|
11
11
|
export * from './systemone-intent.js';
|
|
12
12
|
export * from './intent-stages.js';
|
|
13
|
+
export * from './reply-rank.js';
|
|
13
14
|
export { createActionJournal, createMemoryActionStorage, ActionJournalError } from './action-journal.js';
|
|
14
15
|
export { actionSessionSnapshot, checkpointActionSession } from './action-session.js';
|
|
15
16
|
export { retainRecoveryState, applyRecoveryStatePolicy } from './recovery-policy.js';
|
package/dist/host/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/host/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAC,oBAAoB,EAAC,eAAe,EAAC,kBAAkB,EAAC,MAAM,oBAAoB,CAAC;AAC3F,YAAY,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrD,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/host/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAC,oBAAoB,EAAC,eAAe,EAAC,kBAAkB,EAAC,MAAM,oBAAoB,CAAC;AAC3F,YAAY,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrD,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAEhC,OAAO,EAAC,mBAAmB,EAAC,yBAAyB,EAAC,kBAAkB,EAAC,MAAM,qBAAqB,CAAC;AACrG,OAAO,EAAC,qBAAqB,EAAC,uBAAuB,EAAC,MAAM,qBAAqB,CAAC;AAElF,OAAO,EAAC,mBAAmB,EAAC,wBAAwB,EAAC,MAAM,sBAAsB,CAAC;AAElF,OAAO,EAAC,oBAAoB,EAAC,MAAM,6BAA6B,CAAC;AAEjE,OAAO,EAAC,4BAA4B,EAAC,4BAA4B,EAAC,6BAA6B,EAAC,MAAM,wBAAwB,CAAC;AAE/H,OAAO,EAAC,0BAA0B,EAAC,uBAAuB,EAAC,qBAAqB,EAAC,MAAM,mBAAmB,CAAC;AAE3G,OAAO,EAAC,iBAAiB,EAAC,4BAA4B,EAAC,MAAM,2BAA2B,CAAC"}
|
package/dist/host/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export * from './request.js';
|
|
|
9
9
|
export { validateFlowRecovery, flowHandoffInfo, renderFlowRecovery } from './flow-recovery.js';
|
|
10
10
|
export * from './systemone-intent.js';
|
|
11
11
|
export * from './intent-stages.js';
|
|
12
|
+
export * from './reply-rank.js';
|
|
12
13
|
export { createActionJournal, createMemoryActionStorage, ActionJournalError } from './action-journal.js';
|
|
13
14
|
export { actionSessionSnapshot, checkpointActionSession } from './action-session.js';
|
|
14
15
|
export { retainRecoveryState, applyRecoveryStatePolicy } from './recovery-policy.js';
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reply ranker: what the caller's latest message does with the question a flow asked — the instructions and the
|
|
3
|
+
* decoders, zero runtime imports. jsfe asks it at a contextual DISPATCH (`onResponse: "continue"`), passing the request
|
|
4
|
+
* below to aiCallback as its 4th argument (task 'rank_reply'). The host is the executor: System One (Jev) answers, a
|
|
5
|
+
* text model covers it (the host's aiCallbackRank), and the CANONICAL reply is a JSON string: { "option": "<name>" }.
|
|
6
|
+
*
|
|
7
|
+
* answers the reply answers the question (the default: an open question's reply that clears no option is its answer)
|
|
8
|
+
* abort the caller withdraws the request and asks for nothing else
|
|
9
|
+
* new_request the caller asks for something else: a different service or an unrelated question
|
|
10
|
+
*
|
|
11
|
+
* Both executors' instructions come from the same criteria. A host that does not know this task answers the `llm`
|
|
12
|
+
* instructions with its text model, which is a complete answer.
|
|
13
|
+
*/
|
|
14
|
+
export declare const REPLY_OPTIONS: readonly ["answers", "abort", "new_request"];
|
|
15
|
+
export type ReplyOption = typeof REPLY_OPTIONS[number];
|
|
16
|
+
/** An open question's reply that clears no option is its answer (owner decision 2026-10-26). */
|
|
17
|
+
export declare const REPLY_DEFAULT: ReplyOption;
|
|
18
|
+
/** A noul at or above this is a yes. */
|
|
19
|
+
export declare const REPLY_YES_THRESHOLD = 0.5;
|
|
20
|
+
export interface ReplyTurn {
|
|
21
|
+
role: 'user' | 'assistant';
|
|
22
|
+
content: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ReplyRankRequest {
|
|
25
|
+
task: 'rank_reply';
|
|
26
|
+
input: string;
|
|
27
|
+
conversation: ReplyTurn[];
|
|
28
|
+
/** the flow's question and what answers it (the step's responseContext, interpolated) */
|
|
29
|
+
question: string;
|
|
30
|
+
options: ReplyOption[];
|
|
31
|
+
llm: {
|
|
32
|
+
system: string;
|
|
33
|
+
user: string;
|
|
34
|
+
schema: Record<string, unknown>;
|
|
35
|
+
};
|
|
36
|
+
systemOne: {
|
|
37
|
+
state: Record<string, unknown>;
|
|
38
|
+
questions: Record<string, unknown>;
|
|
39
|
+
index: {
|
|
40
|
+
id: string;
|
|
41
|
+
option: ReplyOption;
|
|
42
|
+
}[];
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** The reply ranker's instructions for both executors. */
|
|
46
|
+
export declare function buildReplyRank(input: string, conversation: ReplyTurn[], question: string): ReplyRankRequest;
|
|
47
|
+
/** A text model's answer as the canonical reply; throws when it names no offered option. */
|
|
48
|
+
export declare function decodeReplyRankLlm(text: string, request: ReplyRankRequest): {
|
|
49
|
+
option: ReplyOption;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* System One's answers as the canonical reply: the highest-rated option when it clears the cut, else the default
|
|
53
|
+
* (`answers`), with every option's probability (for logs). Throws if any question is unanswered.
|
|
54
|
+
*/
|
|
55
|
+
export declare function decodeReplyRankSystemOne(answers: Record<string, {
|
|
56
|
+
noul?: unknown;
|
|
57
|
+
} | undefined> | undefined, request: ReplyRankRequest, { threshold }?: {
|
|
58
|
+
threshold?: number | undefined;
|
|
59
|
+
}): {
|
|
60
|
+
option: ReplyOption;
|
|
61
|
+
scores: Record<string, number>;
|
|
62
|
+
fallback: boolean;
|
|
63
|
+
};
|
|
64
|
+
/** Is this jsfe's reply-rank request (a host's aiCallback receives it as the 4th argument)? */
|
|
65
|
+
export declare function isReplyRankRequest(request: unknown): request is ReplyRankRequest;
|
|
66
|
+
//# sourceMappingURL=reply-rank.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reply-rank.d.ts","sourceRoot":"","sources":["../../src/host/reply-rank.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,aAAa,8CAA+C,CAAC;AAC1E,MAAM,MAAM,WAAW,GAAG,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC;AACvD,gGAAgG;AAChG,eAAO,MAAM,aAAa,EAAE,WAAuB,CAAC;AACpD,wCAAwC;AACxC,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC,MAAM,WAAW,SAAS;IAAG,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AAC1E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,SAAS,EAAE,CAAC;IAC1B,yFAAyF;IACzF,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,GAAG,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;IACvE,SAAS,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,KAAK,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,WAAW,CAAA;SAAE,EAAE,CAAA;KAAE,CAAC;CACjI;AAsBD,0DAA0D;AAC1D,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CA0C3G;AAED,4FAA4F;AAC5F,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,CAMnG;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAAC,GAAG,SAAS,EACnE,OAAO,EAAE,gBAAgB,EACzB,EAAE,SAA+B,EAAE;;CAAK,GACvC;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAa5E;AAED,+FAA+F;AAC/F,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,gBAAgB,CAGhF"}
|