jsfe 1.9.2 → 1.9.4

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.
@@ -164,6 +164,8 @@ export function validateFlowContracts(flows: any, tools?: any[], { externalFlows
164
164
  card_number: {
165
165
  label: string;
166
166
  label_es: string;
167
+ accumulate: boolean;
168
+ accumulate_first: string;
167
169
  profile: {
168
170
  kind: string;
169
171
  min: number;
@@ -1 +1 @@
1
- {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../../src/forms/contracts.js"],"names":[],"mappings":"AAgBA;;;IAGC;AACD;;;;;EAGC;AACD,qGAAqG;AACrG;;QAwCC;AAqBD;;;QAEC;AAgBD;;;;;;;;;QASC;AACD;;;;QAGC;AACD;;;;;;;;QA8BC;AAED,mHAAmH;AACnH,kDAgBC;AAED,+GAA+G;AAC/G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2EC"}
1
+ {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../../src/forms/contracts.js"],"names":[],"mappings":"AAgBA;;;IAGC;AACD;;;;;EAGC;AACD,qGAAqG;AACrG;;QAwCC;AAqBD;;;QAEC;AAgBD;;;;;;;;;QASC;AACD;;;;QAGC;AACD;;;;;;;;QA8BC;AAED,mHAAmH;AACnH,kDAgBC;AAED,+GAA+G;AAC/G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2EC"}
@@ -264,6 +264,8 @@ export namespace FIELD_PRESETS {
264
264
  export { label_9 as label };
265
265
  let label_es_9: string;
266
266
  export { label_es_9 as label_es };
267
+ export let accumulate: boolean;
268
+ export let accumulate_first: string;
267
269
  export namespace profile_9 {
268
270
  let kind_9: string;
269
271
  export { kind_9 as kind };
@@ -1 +1 @@
1
- {"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../../src/forms/flows.js"],"names":[],"mappings":"AA4FA;;;;;;GAMG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0DC;AACD;;;GAGG;AACH;;;;;;EAGC;AAmYD,wHAAwH;AACxH,wEAaC;AACD,4FAA4F;AAC5F;;;EAYC;AACD,sGAAsG;AACtG;;;EAOC;AAniBM,6CAAuG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0tB9G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAOG"}
1
+ {"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../../src/forms/flows.js"],"names":[],"mappings":"AA4FA;;;;;;GAMG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0DC;AACD;;;GAGG;AACH;;;;;;EAGC;AAuYD,wHAAwH;AACxH,wEAaC;AACD,4FAA4F;AAC5F;;;EAYC;AACD,sGAAsG;AACtG;;;EAOC;AAviBM,6CAAuG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8tB9G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAOG"}
@@ -65,7 +65,7 @@ export const FIELD_PRESETS = {
65
65
  city: { label: 'city', label_es: 'ciudad', profile: { kind: 'text' }, question: 'What city?', question_es: '¿En qué ciudad?' },
66
66
  state: { label: 'state', label_es: 'estado', profile: { kind: 'pattern', regex: '^\\s*(?:Alabama|Alaska|Arizona|Arkansas|California|Colorado|Connecticut|Delaware|Florida|Georgia|Hawaii|Idaho|Illinois|Indiana|Iowa|Kansas|Kentucky|Louisiana|Maine|Maryland|Massachusetts|Michigan|Minnesota|Mississippi|Missouri|Montana|Nebraska|Nevada|New Hampshire|New Jersey|New Mexico|New York|North Carolina|North Dakota|Ohio|Oklahoma|Oregon|Pennsylvania|Rhode Island|South Carolina|South Dakota|Tennessee|Texas|Utah|Vermont|Virginia|Washington|West Virginia|Wisconsin|Wyoming|District of Columbia|Puerto Rico|AL|AK|AZ|AR|CA|CO|CT|DE|FL|GA|HI|ID|IL|IN|IA|KS|KY|LA|ME|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|OH|OK|OR|PA|RI|SC|SD|TN|TX|UT|VT|VA|WA|WV|WI|WY|DC|PR)\\s*$' },
67
67
  question: 'What state?', question_es: '¿En qué estado?' },
68
- card_number: { label: 'card number', label_es: 'número de tarjeta', profile: { kind: 'digits', min: 13, max: 19 }, digits: { min: 13, max: 19, complete: [{ first: '4', lengths: [16, 19] }, { first: '25', lengths: [16] }, { first: '3', lengths: [15] }, { first: '6', lengths: [16, 19] }] },
68
+ card_number: { label: 'card number', label_es: 'número de tarjeta', accumulate: true, accumulate_first: '^[2-6]', profile: { kind: 'digits', min: 13, max: 19 }, digits: { min: 13, max: 19, complete: [{ first: '4', lengths: [16, 19] }, { first: '25', lengths: [16] }, { first: '3', lengths: [15] }, { first: '6', lengths: [16, 19] }] },
69
69
  pii: true, validator: 'validate-card-number',
70
70
  question: 'Please say or enter your card number.', question_es: 'Por favor diga o ingrese el número de su tarjeta.',
71
71
  retry: 'Please say or enter the full card number.', retry_es: 'Por favor diga o ingrese el número completo de su tarjeta.' },
@@ -211,7 +211,7 @@ const collectInput = {
211
211
  id: 'collect-input', name: 'CollectInput', version: '1.0.0', scope: 'isolated',
212
212
  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).",
213
213
  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 })),
214
- variables: vars({ 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_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: '' }),
214
+ 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_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: '' }),
215
215
  steps: [
216
216
  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; })()"),
217
217
  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]"),
@@ -251,6 +251,10 @@ const collectInput = {
251
251
  SET('ci_route_to', 'ci_routed', "(ci_spec.route_keys || []).filter(function (r) { return String(cargo.choice || '').indexOf(r.prefix) === 0; }).map(function (r) { return r.flow; })[0] || ''"),
252
252
  WHEN('ci_route_key', 'ci_routed', FLOW('ci_route_key_flow', '{{ci_routed}}', 'call', { form: '{{form}}', input: '{{current}}' })),
253
253
  WHEN('ci_routed_end', 'ci_routed', END('ci_routed_done')),
254
+ // `accumulate` (opt-in, digit inputs): a number said in groups across turns ("4347", "6971 2210"…) is kept and joined;
255
+ // short of the input's minimum the next question is "Go on." Anything that is not digits drops the partial.
256
+ SET('ci_partial_join', 'ci_partial', "(function () { var state = cargo.forms[form]; if (!ci_spec || ci_spec.accumulate !== true || !ci_spec.digits) return ''; var said = String(cargo.choiceText || '').trim(); var data = state.data = state.data || {}; var key = '__partial_' + current; if (!/^[0-9][0-9\\s.,-]*$/.test(said)) { delete data[key]; return ''; } var joined = String(data[key] || '') + said.replace(/[^0-9]/g, ''); var starts = ci_spec.accumulate_first ? new RegExp(ci_spec.accumulate_first) : null; if (!data[key] && starts && !starts.test(joined)) return ''; if (joined.length < Number(ci_spec.digits.min || 0)) { data[key] = joined; var fb = state.feedback || {}; fb[current] = 'partial'; state.feedback = fb; return 'hold'; } delete data[key]; cargo.choiceText = joined; cargo.choice = joined; return 'joined'; })()"),
257
+ WHEN('ci_partial_hold', "ci_partial === 'hold'", END('ci_partial_wait')),
254
258
  SET('ci_build_chain', 'ci_chain', CHAIN_JS),
255
259
  SET('ci_match', 'ci_m', "readFormAnswer(cargo.choiceText, ci_chain, Array.isArray(ci_spec.any_of) ? { form: form, names: ci_names } : { form: form, name: current }, cargo.choice, provides && Array.isArray(questions) ? questions : [])"),
256
260
  // The labels and validators ride on each reading, so the presets stay in this one flow.
@@ -1 +1 @@
1
- {"version":3,"file":"form-conversation.d.ts","sourceRoot":"","sources":["../../src/helpers/form-conversation.js"],"names":[],"mappings":"AAGA,0EAGC;AACD,oEAGC;AACD,sGAAsG;AACtG,+GAMC;AACD,8EAAwI;AAExI,yGAAyG;AACzG;;;;;;;;;;;;;;;;;;;;EAUC;AAED,sGAAsG;AACtG;;;;;;;;;;;;;;;;;;;;EAeC"}
1
+ {"version":3,"file":"form-conversation.d.ts","sourceRoot":"","sources":["../../src/helpers/form-conversation.js"],"names":[],"mappings":"AAGA,0EAGC;AACD,oEAGC;AACD,sGAAsG;AACtG,+GAOC;AACD,8EAAwI;AAExI,yGAAyG;AACzG;;;;;;;;;;;;;;;;;;;;EAUC;AAED,sGAAsG;AACtG;;;;;;;;;;;;;;;;;;;;EAeC"}
@@ -25,6 +25,8 @@ export function formInputPrompt(cargo, id, input, spec, data = {}, language = 'e
25
25
  const es = language === 'es', feedback = cargo.forms?.[id]?.feedback?.[input];
26
26
  const question = spec.question_from && (es ? (data[spec.question_from + '_es'] || data[spec.question_from]) : data[spec.question_from]) || (es ? (spec.question_es || spec.question) : spec.question) || '';
27
27
  const retry = es ? spec.retry_es : spec.retry;
28
+ if (feedback === 'partial')
29
+ return es ? 'Continúe.' : 'Go on.';
28
30
  if (feedback && retry)
29
31
  return retry;
30
32
  return (feedback === 'miss' ? (es ? 'Disculpe, no le entendí. ' : "Sorry, I didn't catch that. ") : '') + question;
@@ -1 +1 @@
1
- export const JSFE_RUNTIME_FINGERPRINT = "sha256:14ff983bcba641d2737a86aa2f7b2fb855991c13a38bdbe20018cdd4ac504050";
1
+ export const JSFE_RUNTIME_FINGERPRINT = "sha256:ea563a7ddbdb680ea2a5acaa1dc4472dbb417ce95e20d7463db1198946c98475";
@@ -1,3 +1,16 @@
1
+ ## 1.9.4: card groups start like a card
2
+
3
+ `accumulate_first` (a regular expression) limits accumulation to a first group that matches it; the `card_number` preset
4
+ uses `^[2-6]` (every card network's first digit), so "1234" or "0729" said at the card question keeps its normal reading
5
+ as an expiration date or a security code.
6
+
7
+ ## 1.9.3: a card number said in groups
8
+
9
+ A digit input may opt in to `accumulate: true`: an answer that is only digits and short of the input's minimum is kept, and
10
+ the next question is "Go on." / "Continúe."; the next digits are joined to it, and anything that is not digits drops the
11
+ partial. The shared `card_number` preset opts in, so a card read in groups across turns is collected whole instead of each
12
+ group being read as another field (an account number, an amount).
13
+
1
14
  ## 1.9.2: address city list and part corrections
2
15
 
3
16
  The shared address form (`collect-geocoded-address`) asks the city when the ZIP code carries several place names (`geocode-zip` returns `localities`, Google's `postcode_localities`; at most 7): "Which city is it: Commerce, East Los Angeles, Los Angeles or Vernon?", by name or key. With one name it is not asked, and a caller who says the city is wrong is asked it plainly and any city is taken. At the read-back, naming one part without a value ("the city is wrong", "la ciudad no") asks only that part again, and a plain NO asks which part is wrong (zip code, city, street, apartment, or ALL to start over) instead of starting over. The part naming is an opt-in form confirmation setting, `confirm.named_fields: true`, read from each input's `names`; other forms keep their behavior. A host whose `geocode-zip` returns no `localities` keeps the one city it returns.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsfe",
3
- "version": "1.9.2",
3
+ "version": "1.9.4",
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",