jsfe 1.14.1 → 1.14.2
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":"AA6HA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4DC;AACD;;;GAGG;AACH;;;;;;EAGC;
|
|
1
|
+
{"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../../src/forms/flows.js"],"names":[],"mappings":"AA6HA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4DC;AACD;;;GAGG;AACH;;;;;;EAGC;AAueD,wHAAwH;AACxH,wEAaC;AACD,4FAA4F;AAC5F;;;EAYC;AACD,sGAAsG;AACtG;;;EAOC;AAnqBM,6CAAuG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA41B9G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAOG"}
|
package/dist/forms/flows.js
CHANGED
|
@@ -269,7 +269,9 @@ const collectInput = {
|
|
|
269
269
|
SET('ci_question', 'ci_q', "formInputPrompt(cargo,form,current,ci_spec,ci_d,'en')"),
|
|
270
270
|
SET('ci_question_es', 'ci_q_es', "formInputPrompt(cargo,form,current,ci_spec,ci_d,'es')"),
|
|
271
271
|
SIDE('ci_feedback_consumed', 'consumeFormFeedback(cargo,form,current)'),
|
|
272
|
-
|
|
272
|
+
// A grouped input (any_of: a plan option, an amount...) offers the ranker every choice its members declare (prod 2026-10-28:
|
|
273
|
+
// the pay offer offered none, so a caller's own words for a listed option were not understood).
|
|
274
|
+
SET('ci_own_choices', 'ci_choices', "ci_spec.choices_from && Array.isArray(ci_d[ci_spec.choices_from]) ? ci_d[ci_spec.choices_from] : (ci_spec.profile.kind === 'choice' && Array.isArray(ci_spec.profile.choices) ? ci_spec.profile.choices : (Array.isArray(ci_spec.any_of) ? ci_spec.any_of.reduce(function(all,m){return all.concat(m && m.profile && m.profile.kind === 'choice' && Array.isArray(m.profile.choices) ? m.profile.choices.filter(function(c,i,own){return !all.some(function(a){return String(a.key)===String(c.key);}) && own.findIndex(function(o){return String(o.key)===String(c.key);})===i;}) : []);},[]) : []))"),
|
|
273
275
|
SET('ci_keypad', 'ci_digits', 'ci_spec.digits || null'),
|
|
274
276
|
SET('ci_is_amount', 'ci_amount', "ci_spec.amount === 'marked' ? 'marked' : (ci_spec.amount === true || ci_spec.profile.kind === 'amount')"),
|
|
275
277
|
// FORM profile readers own normalization and compound matching. Never discard other
|
|
@@ -389,10 +391,18 @@ const readFormResponse = {
|
|
|
389
391
|
// `answers` (picks none): asked again ("Sorry, I didn't catch that."), counted as an attempt so max_attempts still bounds
|
|
390
392
|
// it; `abort` ends every flow; `new_request` goes to intent detection.
|
|
391
393
|
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)");
|
|
392
|
-
|
|
394
|
+
// The ranker is offered only a person. Text is never offered: its label matched "continuar", "texto" and "enlace", and a
|
|
395
|
+
// call moved to text hangs up (prod 2026-10-28: 7 of 7 such picks were wrong). TEXTO and keypad 9 are read before it.
|
|
396
|
+
const ESCAPES_JS = "[{key:'__agent',label:'speak with a person (a live agent)'}]";
|
|
393
397
|
const rankedChoices = (id, from) => SET(id, 'ranked', `(${from}).concat(${ESCAPES_JS})`);
|
|
394
|
-
|
|
395
|
-
|
|
398
|
+
// A choice as the ranker is offered it: its key, and its label or its worded phrases.
|
|
399
|
+
const RANK_LABEL_JS = "function(c){var said=(Array.isArray(c.phrases) ? c.phrases : []).map(String).filter(function(p){return /[a-zà-ÿ]/i.test(p) && p!==String(c.key);}).slice(0,6);return String(c.label || (said.length ? c.key+' ('+said.join(', ')+')' : c.key));}";
|
|
400
|
+
// The choices of this form's OTHER inputs that the answer could also set (the word reader reads them all: 'LO VENCIDO' at
|
|
401
|
+
// the pay confirmation): the ranker is offered every choice the reader would accept (owner 2026-10-28). A confirmation
|
|
402
|
+
// input is answered only when asked, and a key already offered is not offered twice. Each carries its input, so a pick is
|
|
403
|
+
// read as that input's answer.
|
|
404
|
+
const otherChoicesJS = (asked, taken) => `(function(){var seen=(${taken}).map(function(c){return String(c.key);}).concat(['yes','no']);return (chain || []).filter(function(i){return i.form===form && i.name!==${asked} && !i.confirmation && i.profile && i.profile.kind==='choice' && Array.isArray(i.profile.choices);}).reduce(function(all,i){return all.concat(i.profile.choices.filter(function(c){var k=String(c.key);if(seen.indexOf(k)>=0)return false;seen.push(k);return true;}).map(function(c){return {key:String(c.key),label:(${RANK_LABEL_JS})(c),owner:{form:i.form,name:i.name}};}));},[]);})()`;
|
|
405
|
+
const escapeRoutes = (id) => [WHEN(`${id}_agent`, "reply && reply.key === '__agent'", FLOW(`${id}_agent_flow`, 'live-agent-requested', 'reboot'))];
|
|
396
406
|
const rankedDispatch = (id, reason) => ({ ...yieldDispatch(id, 'form', `'${reason}'`), onResponse: 'continue', responseContext: 'Current question: {{asked}}', responseChoices: 'ranked', responseVariable: 'reply' });
|
|
397
407
|
const readFormReply = {
|
|
398
408
|
id: 'read-form-reply', name: 'ReadFormReply', version: '1.1.0', scope: 'isolated', description: 'Rank an unreadable answer against its question: a request for a person or for text is taken; an answer is asked again; a withdrawal or a new request leaves with saved form progress.',
|
|
@@ -405,37 +415,45 @@ const readFormReply = {
|
|
|
405
415
|
rankedMiss('reply_miss')]
|
|
406
416
|
};
|
|
407
417
|
const readFormChoice = {
|
|
408
|
-
id: 'read-form-choice', name: 'ReadFormChoice', version: '1.
|
|
418
|
+
id: 'read-form-choice', name: 'ReadFormChoice', version: '1.3.0', scope: 'isolated', description: 'Rank an unreadable answer to a choice question and take the offered option it picks, or a request for a person or for text; otherwise ask again. A withdrawal or a new request leaves with saved form progress.',
|
|
409
419
|
parameters: [{ name: 'form', type: 'string' }, { name: 'input', type: 'string' }, { name: 'question', type: 'string' }, { name: 'question_es', type: 'string' }, { name: 'choices', type: 'array' }, { name: 'firm', type: 'string' }],
|
|
410
|
-
variables: vars({ question: '', question_es: '', choices: [], firm: '', asked: '', ranked: [], reply: null, picked: '', chain: null, reading: null }),
|
|
420
|
+
variables: vars({ question: '', question_es: '', choices: [], firm: '', asked: '', ranked: [], reply: null, picked: '', chain: null, reading: null, others: [], owner: null }),
|
|
411
421
|
steps: [SET('choice_asked', 'asked', "language === 'es' && question_es ? question_es : question"),
|
|
412
|
-
|
|
422
|
+
SET('choice_chain', 'chain', 'formInputChain(cargo.forms, form, cargo)'),
|
|
423
|
+
SET('choice_others', 'others', otherChoicesJS('input', 'choices || []')),
|
|
424
|
+
rankedChoices('choice_ranked', `(choices || []).filter(function(c,i,all){return !(firm==='firm' && String(c.key)==='yes') && all.findIndex(function(o){return String(o.key)===String(c.key);})===i;}).map(function(c){return {key:String(c.key),label:(${RANK_LABEL_JS})(c)};}).concat(others.map(function(c){return {key:c.key,label:c.label};}))`),
|
|
413
425
|
rankedDispatch('choice_dispatch', 'input_not_matched'),
|
|
414
426
|
...escapeRoutes('choice_escape'),
|
|
415
|
-
SET('choice_picked', 'picked', "(function(){var k=String(reply && reply.key || '');var c=(choices || []).filter(function(c){return String(c.key)===k;})[0];return c ? String(c.key) : '';})()"),
|
|
427
|
+
SET('choice_picked', 'picked', "(function(){var k=String(reply && reply.key || '');var c=(choices || []).filter(function(c){return String(c.key)===k;})[0] || others.filter(function(c){return c.key===k;})[0];return c ? String(c.key) : '';})()"),
|
|
428
|
+
SET('choice_owner', 'owner', "(others.filter(function(c){return c.key===picked && !(choices || []).some(function(own){return String(own.key)===picked;});})[0] || {}).owner || null"),
|
|
416
429
|
WHEN('choice_none', '!picked', rankedMiss('choice_miss')),
|
|
417
430
|
WHEN('choice_none_end', '!picked', END('choice_none_done')),
|
|
418
431
|
SIDE('choice_take', "(cargo.choice=picked,cargo.choiceText=picked,true)"),
|
|
419
|
-
SET('
|
|
420
|
-
SET('choice_read', 'reading', "enrichFormMatches(readFormAnswer(picked, chain, { form: form, name: input }, picked, []), chain, form, [input], input)"),
|
|
432
|
+
SET('choice_read', 'reading', "owner ? enrichFormMatches(readFormAnswer(picked, chain, owner, picked, []), chain, form, [owner.name], owner.name) : enrichFormMatches(readFormAnswer(picked, chain, { form: form, name: input }, picked, []), chain, form, [input], input)"),
|
|
421
433
|
WHEN('choice_unread', '!reading.hits.length', rankedMiss('choice_unread_miss')),
|
|
422
434
|
WHEN('choice_unread_end', '!reading.hits.length', END('choice_unread_done')),
|
|
423
435
|
SIDE('choice_clear', 'clearFormMisses(cargo,form,input)'),
|
|
424
436
|
FLOW('choice_accept', 'collect-input-accept', 'call', { hits: '{{reading.hits}}' })]
|
|
425
437
|
};
|
|
426
438
|
// A confirmation's unread answer goes to the reply ranker with the confirmation question (owner 2026-10-27), never
|
|
427
|
-
// straight to the host. A form's confirmation is exact (it authorizes the form's action), so the ranker is offered NO
|
|
428
|
-
// and
|
|
429
|
-
// flow; `new_request` goes to intent detection.
|
|
439
|
+
// straight to the host. A form's confirmation is exact (it authorizes the form's action), so the ranker is offered NO, the
|
|
440
|
+
// form's other choices (a correction) and a person, never YES. Result: cargo.confirmRanked = 'no' | 'changed' | 'none'
|
|
441
|
+
// (ConfirmForm reads it); `abort` ends every flow; `new_request` goes to intent detection.
|
|
430
442
|
const readConfirmReply = {
|
|
431
|
-
id: 'read-confirm-reply', name: 'ReadConfirmReply', version: '1.
|
|
443
|
+
id: 'read-confirm-reply', name: 'ReadConfirmReply', version: '1.2.0', scope: 'isolated', description: 'Rank an unreadable answer to a confirmation: it declines (NO), picks another of the form\'s choices (a correction, read as that input\'s answer), asks for a person, or is asked again; a withdrawal or a new request leaves with saved form progress. Never confirms.',
|
|
432
444
|
parameters: [{ name: 'form', type: 'string' }, { name: 'question', type: 'string' }, { name: 'question_es', type: 'string' }],
|
|
433
|
-
variables: vars({ question: '', question_es: '', asked: '', ranked: [], reply: null }),
|
|
445
|
+
variables: vars({ question: '', question_es: '', asked: '', ranked: [], reply: null, chain: null, others: [], picked: null, reading: null }),
|
|
434
446
|
steps: [SET('confirm_asked', 'asked', "language === 'es' && question_es ? question_es : question"),
|
|
435
|
-
|
|
447
|
+
SET('confirm_chain', 'chain', 'formInputChain(cargo.forms, form, cargo)'),
|
|
448
|
+
SET('confirm_others', 'others', otherChoicesJS("''", '[]')),
|
|
449
|
+
rankedChoices('confirm_ranked_choices', "[{key:'no',label:'no: declines or refuses what the question asks to confirm'}].concat(others.map(function(c){return {key:c.key,label:c.label};}))"),
|
|
436
450
|
rankedDispatch('confirm_dispatch', 'confirmation_not_matched'),
|
|
437
451
|
...escapeRoutes('confirm_escape'),
|
|
438
|
-
|
|
452
|
+
// a correction picked by the ranker is read as its input's answer: the form changes and is confirmed again, never confirmed
|
|
453
|
+
SET('confirm_picked', 'picked', "others.filter(function(c){return reply && c.key===reply.key;})[0] || null"),
|
|
454
|
+
SET('confirm_read', 'reading', "picked ? enrichFormMatches(readFormAnswer(picked.key, chain, picked.owner, picked.key, []), chain, form, [picked.owner.name], picked.owner.name) : null"),
|
|
455
|
+
WHEN('confirm_change', 'reading && reading.hits.length', FLOW('confirm_change_accept', 'collect-input-accept', 'call', { hits: '{{reading.hits}}' })),
|
|
456
|
+
SIDE('confirm_ranked', "(cargo.confirmRanked=reading && reading.hits.length ? 'changed' : reply && reply.key==='no' ? 'no' : 'none',true)")]
|
|
439
457
|
};
|
|
440
458
|
const collectContextualAnswer = formFlow({ id: 'collect-contextual-answer', name: 'CollectContextualAnswer', description: 'Collect one open-ended answer using contextual DISPATCH and preserve the caller wording.',
|
|
441
459
|
parameters: [...['question', 'question_es', 'description', 'context', 'context_es'].map(name => ({ name, type: 'string' })), { name: 'choices', type: 'array' }, { name: 'digits', type: 'object' }],
|
|
@@ -26,6 +26,11 @@ export declare const CHOICE_PREFIX = "choice:";
|
|
|
26
26
|
export declare const REPLY_DEFAULT: ReplyOption;
|
|
27
27
|
/** A noul at or above this is a yes. */
|
|
28
28
|
export declare const REPLY_YES_THRESHOLD = 0.5;
|
|
29
|
+
/**
|
|
30
|
+
* The cut an option must clear, by what a wrong pick costs: an option not listed here takes REPLY_YES_THRESHOLD. Set
|
|
31
|
+
* from labelled prod replies (owner 2026-10-28), never guessed.
|
|
32
|
+
*/
|
|
33
|
+
export declare const REPLY_CUTS: Partial<Record<ReplyOption, number>>;
|
|
29
34
|
export interface ReplyTurn {
|
|
30
35
|
role: 'user' | 'assistant';
|
|
31
36
|
content: string;
|
|
@@ -60,15 +65,15 @@ export declare function decodeReplyRankLlm(text: string, request: ReplyRankReque
|
|
|
60
65
|
option: ReplyOption;
|
|
61
66
|
};
|
|
62
67
|
/**
|
|
63
|
-
* System One's answers as the canonical reply: the highest-rated option
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* choice 0.65 and `answers` 0.81, and the menu was asked again). Throws if any question is unanswered.
|
|
68
|
+
* System One's answers as the canonical reply: the highest-rated option that clears its own cut (REPLY_CUTS, else the
|
|
69
|
+
* threshold), a cleared choice before `answers` and `new_request`; when none does, the default (`answers`: asked again at a choice question, the answer at an open one), with
|
|
70
|
+
* every option's probability (for logs). Throws if any question is unanswered.
|
|
67
71
|
*/
|
|
68
72
|
export declare function decodeReplyRankSystemOne(answers: Record<string, {
|
|
69
73
|
noul?: unknown;
|
|
70
|
-
} | undefined> | undefined, request: ReplyRankRequest, { threshold }?: {
|
|
71
|
-
threshold?: number
|
|
74
|
+
} | undefined> | undefined, request: ReplyRankRequest, { threshold, cuts }?: {
|
|
75
|
+
threshold?: number;
|
|
76
|
+
cuts?: Partial<Record<ReplyOption, number>>;
|
|
72
77
|
}): {
|
|
73
78
|
option: ReplyOption;
|
|
74
79
|
scores: Record<string, number>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reply-rank.d.ts","sourceRoot":"","sources":["../../src/host/reply-rank.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,eAAO,MAAM,aAAa,8CAA+C,CAAC;AAC1E,kEAAkE;AAClE,MAAM,MAAM,WAAW,GAAG,OAAO,aAAa,CAAC,MAAM,CAAC,GAAG,UAAU,MAAM,EAAE,CAAC;AAC5E,0FAA0F;AAC1F,MAAM,WAAW,WAAW;IAAG,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AAC3D,eAAO,MAAM,aAAa,YAAY,CAAC;AACvC,gGAAgG;AAChG,eAAO,MAAM,aAAa,EAAE,WAAuB,CAAC;AACpD,wCAAwC;AACxC,eAAO,MAAM,mBAAmB,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"reply-rank.d.ts","sourceRoot":"","sources":["../../src/host/reply-rank.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,eAAO,MAAM,aAAa,8CAA+C,CAAC;AAC1E,kEAAkE;AAClE,MAAM,MAAM,WAAW,GAAG,OAAO,aAAa,CAAC,MAAM,CAAC,GAAG,UAAU,MAAM,EAAE,CAAC;AAC5E,0FAA0F;AAC1F,MAAM,WAAW,WAAW;IAAG,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AAC3D,eAAO,MAAM,aAAa,YAAY,CAAC;AACvC,gGAAgG;AAChG,eAAO,MAAM,aAAa,EAAE,WAAuB,CAAC;AACpD,wCAAwC;AACxC,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAO3D,CAAC;AAEF,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,0DAA0D;IAC1D,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,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;AAwCD,0DAA0D;AAC1D,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,WAAW,EAAO,GAAG,gBAAgB,CA+CxI;AAED,4FAA4F;AAC5F,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,CAMnG;AAED;;;;GAIG;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,IAAiB,EAAE,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAA;CAAO,GAC/H;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAiB5E;AAED,+FAA+F;AAC/F,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,gBAAgB,CAGhF"}
|
package/dist/host/reply-rank.js
CHANGED
|
@@ -19,6 +19,18 @@ export const CHOICE_PREFIX = 'choice:';
|
|
|
19
19
|
export const REPLY_DEFAULT = 'answers';
|
|
20
20
|
/** A noul at or above this is a yes. */
|
|
21
21
|
export const REPLY_YES_THRESHOLD = 0.5;
|
|
22
|
+
/**
|
|
23
|
+
* The cut an option must clear, by what a wrong pick costs: an option not listed here takes REPLY_YES_THRESHOLD. Set
|
|
24
|
+
* from labelled prod replies (owner 2026-10-28), never guessed.
|
|
25
|
+
*/
|
|
26
|
+
export const REPLY_CUTS = {
|
|
27
|
+
// a NO cancels what the caller was confirming. Measured 2026-10-28: replies that are not a NO ("imposible", "no es un")
|
|
28
|
+
// rated 0.51-0.63, real NOs ("No me interesa", "No de verdad", "mejor no") 0.67-0.95
|
|
29
|
+
'choice:no': 0.65,
|
|
30
|
+
// a new request leaves the question unanswered. Unclear replies at a question ("la curazán" at a confirmation, "Es que
|
|
31
|
+
// mi tarjeta se cambió" at the pay menu) rated 0.47-0.58 and vary ±0.04 between calls; they are asked again
|
|
32
|
+
new_request: 0.6,
|
|
33
|
+
};
|
|
22
34
|
// ── the criteria: written once, rendered for both executors ─────────────────────────────────────────
|
|
23
35
|
const CRITERIA = {
|
|
24
36
|
answers: {
|
|
@@ -81,7 +93,12 @@ export function buildReplyRank(input, conversation, question, choices = []) {
|
|
|
81
93
|
].join('\n');
|
|
82
94
|
const questions = {};
|
|
83
95
|
const index = [];
|
|
84
|
-
|
|
96
|
+
// With declared choices, `answers` ("picks none of them") is not a question System One answers: it competes with the
|
|
97
|
+
// choices it excludes. It is what is left when no option clears its cut (owner 2026-10-28). A text model still has it.
|
|
98
|
+
// An escape (`__agent`) is not the question's own choice: a question that offers only escapes is an open question,
|
|
99
|
+
// where `answers` is the reply (prod 2026-10-28: a city named at "check nearby stores" went to intent detection).
|
|
100
|
+
const ownChoices = choices.some(c => !c.key.startsWith('__'));
|
|
101
|
+
options.filter(o => !(o === 'answers' && ownChoices)).forEach((o, i) => {
|
|
85
102
|
const id = `r${i}`;
|
|
86
103
|
questions[id] = { type: 'noul', criteria: criteriaOf(o, choices), instructions: { option: o, question: `Does the latest message do what \`${o}\` describes?` } };
|
|
87
104
|
index.push({ id, option: o });
|
|
@@ -110,12 +127,11 @@ export function decodeReplyRankLlm(text, request) {
|
|
|
110
127
|
return { option };
|
|
111
128
|
}
|
|
112
129
|
/**
|
|
113
|
-
* System One's answers as the canonical reply: the highest-rated option
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* choice 0.65 and `answers` 0.81, and the menu was asked again). Throws if any question is unanswered.
|
|
130
|
+
* System One's answers as the canonical reply: the highest-rated option that clears its own cut (REPLY_CUTS, else the
|
|
131
|
+
* threshold), a cleared choice before `answers` and `new_request`; when none does, the default (`answers`: asked again at a choice question, the answer at an open one), with
|
|
132
|
+
* every option's probability (for logs). Throws if any question is unanswered.
|
|
117
133
|
*/
|
|
118
|
-
export function decodeReplyRankSystemOne(answers, request, { threshold = REPLY_YES_THRESHOLD } = {}) {
|
|
134
|
+
export function decodeReplyRankSystemOne(answers, request, { threshold = REPLY_YES_THRESHOLD, cuts = REPLY_CUTS } = {}) {
|
|
119
135
|
const scores = {};
|
|
120
136
|
for (const q of request.systemOne.index) {
|
|
121
137
|
const noul = Number(answers?.[q.id]?.noul);
|
|
@@ -123,10 +139,12 @@ export function decodeReplyRankSystemOne(answers, request, { threshold = REPLY_Y
|
|
|
123
139
|
throw new Error(`System One: question ${q.id} unanswered`);
|
|
124
140
|
scores[q.option] = noul;
|
|
125
141
|
}
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
142
|
+
const cleared = request.systemOne.index.map(q => q.option).filter(o => scores[o] >= (cuts[o] ?? threshold)).sort((a, b) => scores[b] - scores[a]);
|
|
143
|
+
// A cleared choice is taken over `answers` and `new_request` (owner 2026-10-28: a request that names an offered option
|
|
144
|
+
// is that option, "Representative." is the agent); only `abort` keeps its rank.
|
|
145
|
+
const choice = cleared.find(o => o.startsWith(CHOICE_PREFIX));
|
|
146
|
+
const best = choice && (cleared[0] === 'answers' || cleared[0] === 'new_request') ? choice : cleared[0];
|
|
147
|
+
if (best) {
|
|
130
148
|
return { option: best, scores, fallback: false };
|
|
131
149
|
}
|
|
132
150
|
else {
|
package/dist/runtime-identity.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const JSFE_RUNTIME_FINGERPRINT = "sha256:
|
|
1
|
+
export const JSFE_RUNTIME_FINGERPRINT = "sha256:9fca8fe719c5610d1260ece2cc393107d549667095f71bcaa62427a894f0ba7b";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsfe",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.2",
|
|
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",
|