jsfe 1.19.0 → 1.19.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
CHANGED
|
@@ -398,6 +398,12 @@ reasonFor })` builds both executors' instructions. A reason question declares no
|
|
|
398
398
|
unchanged: the generic withdrawal includes "I no longer want it", which at a reason question withdrew three prod
|
|
399
399
|
cancellations.
|
|
400
400
|
|
|
401
|
+
**Digits said in groups (jsfe 1.19.1):** every `digits` input reads a comma, or a period and a space, after a group of
|
|
402
|
+
one to three digits as a pause inside one number, because speech-to-text writes spoken groups that way ("926, 1 0." is
|
|
403
|
+
the ZIP 92610, "7, 9, 984. 5." the code 799845). A comma after a longer number still separates two numbers ("92610,
|
|
404
|
+
56035214"). At its own question, an answer of digits and separators alone is one number when its length fits ("4111,
|
|
405
|
+
1111, 1111, 1111"). A choice still reads "1, 2" as two keys.
|
|
406
|
+
|
|
401
407
|
**Choices that leave (`route: 'new_request'`) and staying on noise (`onNoIntent: 'stay'`, jsfe 1.15.3):** a choice
|
|
402
408
|
declared `{ key, label, route: 'new_request' }` is offered like any other, and picking it leaves like a new request
|
|
403
409
|
(intent detection decides where to). A contextual DISPATCH declared `onNoIntent: 'stay'` asks intent detection BEFORE
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"match-inputs.d.ts","sourceRoot":"","sources":["../../src/helpers/match-inputs.js"],"names":[],"mappings":"AAiBA;;;;GAIG;AACH,sEAQC;
|
|
1
|
+
{"version":3,"file":"match-inputs.d.ts","sourceRoot":"","sources":["../../src/helpers/match-inputs.js"],"names":[],"mappings":"AAiBA;;;;GAIG;AACH,sEAQC;AAuFD;;;GAGG;AACH,sFA8GC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;mHACmH;AACnH;;;;;;;;;;;;;;;;;;;;;EAeC;AACD;;;;;;;;;;;;;;;;;;;;;;EAoOC"}
|
|
@@ -33,10 +33,16 @@ export function formAcceptanceTrace(previous, matches) {
|
|
|
33
33
|
function unescape(answer) {
|
|
34
34
|
return String(answer ?? '').replace(/\\(['"\\])/g, '$1');
|
|
35
35
|
}
|
|
36
|
-
/**
|
|
36
|
+
/** A run of digits as said: spoken digits come apart ("4 3 2 1", "555-123-4567"), so a single space, dash or dot inside a run
|
|
37
|
+
* joins it, and so does a comma (or a period and a space) after a group of one to three digits: speech-to-text writes the
|
|
38
|
+
* pauses between spoken groups that way (prod 2026-10-31: the ZIP 92610 said as "926, 1 0.", the code as "7, 9, 984. 5."). A comma after a
|
|
39
|
+
* longer number separates two numbers ("92610, 56035214"). */
|
|
40
|
+
const DIGIT_RUN = /\d(?:[\s.-]?\d|(?<=(?:^|\D)\d{1,3})(?:,\s?|\.\s)(?=\d))*/g;
|
|
37
41
|
function digitRuns(raw) {
|
|
38
|
-
return (raw.match(
|
|
42
|
+
return (raw.match(DIGIT_RUN) || []).map((r) => r.replace(/\D/g, ''));
|
|
39
43
|
}
|
|
44
|
+
// A choice reads its keys without the comma join: "1, 2" names two keys, not 12.
|
|
45
|
+
const CHOICE_RUN = /\d(?:[\s.-]?\d)*/g;
|
|
40
46
|
const DECIMAL_AMOUNT = /(?:[$€£]\s?)?\d+[.,]\d{2}(?!\d)/g; // a written decimal amount, with its currency mark
|
|
41
47
|
/** Where the answer says amount n: a written decimal by itself ("51 72.45" is not one spoken run), else its number spans. */
|
|
42
48
|
function amountSpans(raw, n) {
|
|
@@ -130,7 +136,7 @@ export function readInputProfile(profile, answer, isCurrent = true) {
|
|
|
130
136
|
if (kind === 'choice') {
|
|
131
137
|
// An amount's own digits are the amount's: a number beside it ("51:72.45", sub account 51 and $72.45) is still a choice.
|
|
132
138
|
const free = isAmountAnswer(raw) ? amountMasked(raw) : raw;
|
|
133
|
-
const runs =
|
|
139
|
+
const runs = (free.match(CHOICE_RUN) || []).map((r) => r.replace(/\D/g, '')), joined = free.replace(CHOICE_RUN, run => ' ' + run.replace(/\D/g, '') + ' ');
|
|
134
140
|
const choices = (Array.isArray(profile.choices) ? profile.choices : []).filter(c => isCurrent || c.current_only !== true).map(c => ({ ...c,
|
|
135
141
|
numericWords: (c.phrases ?? c.words ?? []).filter(word => /^\d{2,}$/.test(String(word).trim())),
|
|
136
142
|
phrases: (c.phrases ?? c.words ?? []).concat(isCurrent ? c.keys || [] : []).filter(word => !(free !== raw && /^\d+$/.test(String(word).trim()) && !runs.includes(String(word).trim())) && (isCurrent || profile.numeric_values === true || !/^[\d*#]$/.test(String(word).trim()))) }));
|
|
@@ -208,7 +214,14 @@ export function readInputProfile(profile, answer, isCurrent = true) {
|
|
|
208
214
|
const min = Number(profile.min || 1);
|
|
209
215
|
const max = Number(profile.max || min);
|
|
210
216
|
// Out of order a decimal amount ("72.45", "287,15") is never this field's digits.
|
|
211
|
-
|
|
217
|
+
let fits = [...new Set(digitRuns(isCurrent ? raw : raw.replace(DECIMAL_AMOUNT, (m) => ' '.repeat(m.length))).filter((d) => d.length >= min && d.length <= max))];
|
|
218
|
+
// At its own question, an answer of digits and separators alone is one number when its length fits ("4111, 1111,
|
|
219
|
+
// 1111, 1111": a card read in groups of four).
|
|
220
|
+
const whole = raw.replace(/\D/g, '');
|
|
221
|
+
if (!fits.length && isCurrent && /^[\d\s.,-]+$/.test(raw.trim()) && whole.length >= min && whole.length <= max) {
|
|
222
|
+
fits = [whole];
|
|
223
|
+
}
|
|
224
|
+
else { /* read by runs */ }
|
|
212
225
|
if (fits.length === 1) {
|
|
213
226
|
return { value: fits[0], spans: numberSpans(raw, fits[0]), evidence: [`d:${fits[0]}`] };
|
|
214
227
|
}
|
|
@@ -341,7 +354,7 @@ export function matchInputs(answer, candidates, current, choiceKey) {
|
|
|
341
354
|
const repeatedChoice = known && !isCur(c) && c.profile.kind === 'choice';
|
|
342
355
|
const isChoice = !isCur(c) && c.profile.kind === 'choice';
|
|
343
356
|
const wholeOptions = isChoice ? (c.profile.choices || []).filter(choice => choice.key === option.value).map(choice => ({ ...choice, exact: true })) : [];
|
|
344
|
-
const wholeChoice = isChoice && [raw, raw.replace(
|
|
357
|
+
const wholeChoice = isChoice && [raw, raw.replace(CHOICE_RUN, run => run.replace(/\D/g, ''))].some(text => pickChoice(text, wholeOptions, false) === option.value);
|
|
345
358
|
readings.push({ form: c.form, name: c.name, value: option.value, ...(option.refused === true ? { refused: true } : {}), ...(option.incompleteMoney === true ? { incompleteMoney: true } : {}), known, repeatedChoice, wholeChoice, choice: isChoice, narrative: isCur(c) && ['text', 'choice'].includes(c.profile.kind) && c.profile.narrative === true, resetOn: Array.isArray(c.reset_on) ? c.reset_on : [], spans: option.spans, keypad: option.keypad === true, fallback: option.fallback === true,
|
|
346
359
|
...(r.alternatives ? { option_label: option.option_label, option_label_es: option.option_label_es } : {}), group: c.group, exclusive: c.exclusive === true, level: forms.indexOf(c.scope || c.form), current: isCur(c), confirm: Array.isArray(c.confirm_against) ? c.confirm_against : [] });
|
|
347
360
|
}
|
|
@@ -359,7 +372,7 @@ export function matchInputs(answer, candidates, current, choiceKey) {
|
|
|
359
372
|
// so the last four of "88 91 7112" fill nothing. A written decimal is a run of its own ("51 72.45": 51, then 72.45).
|
|
360
373
|
const decimalRuns = [...raw.matchAll(DECIMAL_AMOUNT)].map((m) => [m.index, m.index + m[0].length]);
|
|
361
374
|
const undecimal = decimalRuns.reduce((text, [a, b]) => text.slice(0, a) + ' '.repeat(b - a) + text.slice(b), raw);
|
|
362
|
-
const runs = [...undecimal.matchAll(
|
|
375
|
+
const runs = [...undecimal.matchAll(DIGIT_RUN)].map((m) => [m.index, m.index + m[0].replace(/\s+$/, '').length]).concat(decimalRuns);
|
|
363
376
|
const wholeRuns = (reading) => runs.every(([a, b]) => !(reading.spans || []).some(([s, e]) => s < b && a < e) || (reading.spans || []).some(([s, e]) => s <= a && e >= b));
|
|
364
377
|
// Rule 4: a bare number has no money mark and no word in its evidence. Written cents are a money mark: "51:72.45" at
|
|
365
378
|
// the sub account question is sub account 51 and the amount 72.45 (prod 2026-10-30).
|
|
@@ -371,7 +384,7 @@ export function matchInputs(answer, candidates, current, choiceKey) {
|
|
|
371
384
|
// A choice is excluded (a digit it does not offer reaches the invited amount, rule 4), and so is an amount.
|
|
372
385
|
const bareDigits = raw.replace(/\D/g, '');
|
|
373
386
|
const shapeOf = own && own.digits && !['choice', 'amount', 'text'].includes(own.profile.kind) ? own.digits : null;
|
|
374
|
-
if (shapeOf && bareDigits && /^[\d\s
|
|
387
|
+
if (shapeOf && bareDigits && /^[\d\s.,/-]+$/.test(raw.trim()) && !isAmountAnswer(raw) && bareDigits.length >= Number(shapeOf.min || 1) && bareDigits.length <= Number(shapeOf.max || shapeOf.min || 1)
|
|
375
388
|
&& !readings.some((reading) => reading.current || invited(reading))) {
|
|
376
389
|
readings.push({ form: own.form, name: own.name, value: bareDigits, ...(own.validator ? {} : { invalid: true }), known: false, repeatedChoice: false, wholeChoice: false, choice: false, narrative: false, resetOn: [], spans: [[0, raw.length]], keypad: false, fallback: false,
|
|
377
390
|
group: own.group, exclusive: own.exclusive === true, level: forms.indexOf(own.scope || own.form), current: true, confirm: Array.isArray(own.confirm_against) ? own.confirm_against : [] });
|
package/dist/runtime-identity.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const JSFE_RUNTIME_FINGERPRINT = "sha256:
|
|
1
|
+
export const JSFE_RUNTIME_FINGERPRINT = "sha256:f99ae1f2c338d0ef94a77bb5c074d8d015d853556943e2c26a3ecb6af5cc65df";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsfe",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.1",
|
|
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",
|