jsfe 1.37.1 → 1.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/flow-docs.js +1 -1
- package/dist/forms/contracts.js +1 -1
- package/dist/forms/flows.d.ts +4 -3
- package/dist/forms/flows.d.ts.map +1 -1
- package/dist/forms/flows.js +20 -10
- package/dist/helpers/proposal-contract.d.ts.map +1 -1
- package/dist/helpers/proposal-contract.js +3 -1
- package/dist/runtime-identity.js +1 -1
- package/docs/architecture-guide.md +7 -5
- package/docs/copilot-reference.md +1 -1
- package/package.json +1 -1
package/dist/docs/flow-docs.js
CHANGED
|
@@ -366,7 +366,7 @@ export function generateFlowDocs({ sets, root, check = false, hostRun = {}, log
|
|
|
366
366
|
c.then({ label: `${i.name}${i.label ? ' (' + i.label + ')' : ''}${extra ? ': ' + extra : ''}`, kind: 'input' });
|
|
367
367
|
}
|
|
368
368
|
if (fm.confirm) {
|
|
369
|
-
c.then({ label: `confirm (${confirmationFirmness(performs)})`, kind: 'decision' });
|
|
369
|
+
c.then({ label: `confirm (${['firm', 'soft'].includes(fm.confirm?.confirmation) ? fm.confirm.confirmation : confirmationFirmness(performs)})`, kind: 'decision' });
|
|
370
370
|
}
|
|
371
371
|
else { /* no confirmation */ }
|
|
372
372
|
if (fm.after && fm.after.length) {
|
package/dist/forms/contracts.js
CHANGED
|
@@ -137,7 +137,7 @@ export function defineProvider({ provides, contract, parameters = [], variables
|
|
|
137
137
|
{ id: `${flow.id}_provide`, type: 'FLOW', value: 'provide-data', callType: 'call', parameters: { provides: '{{provides}}', asked: '{{asked}}', reply: '{{userInput}}', ...(gateParameters !== undefined ? { gate_params: '{{provider_gate_arguments}}' } : {}) } }] }, { role: 'provider', ...contract, catalog: 'provides' });
|
|
138
138
|
}
|
|
139
139
|
export function defineForm({ contract, provides, ...spec }) {
|
|
140
|
-
const flow = formFlow({ ...spec, confirm_soft:
|
|
140
|
+
const flow = formFlow({ ...spec, confirm_soft: confirmationFirmness([]) === 'soft', ...(provides ? { extraVars: { ...spec.extraVars, provides, questions: providerQuestions(provides) } } : {}) });
|
|
141
141
|
return declared(flow, { role: 'collector', ...contract, form: { inputs: 'inputs' }, ...(provides ? { catalog: 'provides' } : {}) });
|
|
142
142
|
}
|
|
143
143
|
export function definePerformer({ performs, contract, prepare, present, project = [], provides, ...spec }) {
|
package/dist/forms/flows.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The form shell: declare inputs, run the loop, hand the result on. `after` = the form's own steps once complete.
|
|
3
3
|
* `confirm` ({ question, question_es } — SAY-style templates that may read cargo.forms[<id>].values; build them in
|
|
4
4
|
* `before_confirm` SET steps when they need computing): the confirmation, asked every time after the loop and
|
|
5
|
-
* never an input. It is
|
|
5
|
+
* never an input. It is soft unless `confirm_soft` is false (confirmationFirmness: firm only for a payment): a soft
|
|
6
6
|
* confirmation's YES is read as a soft input confirmation's and is offered to the reply ranker; a firm one's is neither. YES runs `after`; NO runs `declined`; a correction given there goes through the form's own matching
|
|
7
7
|
* and the form runs again (so whatever the correction made missing is asked).
|
|
8
8
|
*/
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
*/
|
|
14
14
|
/** rank_parameters and a choice's parameters: [{ name, description, type?, reads? }] (reads: the input the value answers). */
|
|
15
15
|
export function validateRankParameters(id: any, name: any, what: any, list: any): void;
|
|
16
|
-
/** How firm a form's confirmation is: firm when an action it authorizes
|
|
17
|
-
*
|
|
16
|
+
/** How firm a form's confirmation is: firm ONLY when an action it authorizes is a payment (effect 'charge'); every other
|
|
17
|
+
* confirmation is soft, a plain form's (no action) included (owner 2026-11-09). confirm.exact is separate: it binds the
|
|
18
|
+
* action to the confirmed values, whatever the confirmation's firmness. */
|
|
18
19
|
export function confirmationFirmness(performs: any): "firm" | "soft";
|
|
19
20
|
export function formFlow({ id, name, version, description, inputs, after, extraVars, parameters, confirm, confirm_soft, before_confirm, declined, stop, close_on_stop, on_no, resume_on_parent_change, before, done, primary, scope, validator, confirmation_condition, prompt, prompt_es, incomplete_outcome, on_exhausted, must_complete, first_task, first_task_es, acknowledge }: {
|
|
20
21
|
id: any;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../../src/forms/flows.js"],"names":[],"mappings":"AA6GA;;;;;;;GAOG;AACH;;;;GAIG;AACH,8HAA8H;AAC9H,uFAIC;AAiBD;
|
|
1
|
+
{"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../../src/forms/flows.js"],"names":[],"mappings":"AA6GA;;;;;;;GAOG;AACH;;;;GAIG;AACH,8HAA8H;AAC9H,uFAIC;AAiBD;;2EAE2E;AAC3E,qEAEC;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqEC;AACD;;;GAGG;AACH;;;;;;EAGC;AAslBD,wHAAwH;AACxH,wEAaC;AACD,4FAA4F;AAC5F;;;EAYC;AACD,sGAAsG;AACtG;;;EAOC;AApzBM,6CAAuG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6gC9G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAOG"}
|
package/dist/forms/flows.js
CHANGED
|
@@ -105,7 +105,7 @@ const FIELD_COLLECTORS = { 'collect-phone': 'phone', 'collect-email': 'email', '
|
|
|
105
105
|
* The form shell: declare inputs, run the loop, hand the result on. `after` = the form's own steps once complete.
|
|
106
106
|
* `confirm` ({ question, question_es } — SAY-style templates that may read cargo.forms[<id>].values; build them in
|
|
107
107
|
* `before_confirm` SET steps when they need computing): the confirmation, asked every time after the loop and
|
|
108
|
-
* never an input. It is
|
|
108
|
+
* never an input. It is soft unless `confirm_soft` is false (confirmationFirmness: firm only for a payment): a soft
|
|
109
109
|
* confirmation's YES is read as a soft input confirmation's and is offered to the reply ranker; a firm one's is neither. YES runs `after`; NO runs `declined`; a correction given there goes through the form's own matching
|
|
110
110
|
* and the form runs again (so whatever the correction made missing is asked).
|
|
111
111
|
*/
|
|
@@ -145,12 +145,13 @@ function validateAcceptanceDeclarations(id, inputs) {
|
|
|
145
145
|
else { /* no invites, or a list of names */ }
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
|
-
/** How firm a form's confirmation is: firm when an action it authorizes
|
|
149
|
-
*
|
|
148
|
+
/** How firm a form's confirmation is: firm ONLY when an action it authorizes is a payment (effect 'charge'); every other
|
|
149
|
+
* confirmation is soft, a plain form's (no action) included (owner 2026-11-09). confirm.exact is separate: it binds the
|
|
150
|
+
* action to the confirmed values, whatever the confirmation's firmness. */
|
|
150
151
|
export function confirmationFirmness(performs) {
|
|
151
|
-
return Array.isArray(performs) && performs.
|
|
152
|
+
return Array.isArray(performs) && performs.some((action) => action?.effect === 'charge') ? 'firm' : 'soft';
|
|
152
153
|
}
|
|
153
|
-
export function formFlow({ id, name, version, description, inputs, after = [], extraVars = {}, parameters = [], confirm = null, confirm_soft =
|
|
154
|
+
export function formFlow({ id, name, version, description, inputs, after = [], extraVars = {}, parameters = [], confirm = null, confirm_soft = confirmationFirmness([]) === 'soft', before_confirm = [], declined = [], stop = '', close_on_stop = false, on_no = '', resume_on_parent_change = false, before = [], done = true, primary = false, scope = 'isolated', validator = '', confirmation_condition = 'true', prompt = undefined, prompt_es = undefined, incomplete_outcome = null, on_exhausted = null, must_complete = false, first_task = '', first_task_es = '', acknowledge = 'all' }) {
|
|
154
155
|
validateFormExhaustion(on_exhausted);
|
|
155
156
|
if (acknowledge !== 'all' && acknowledge !== 'data')
|
|
156
157
|
throw new Error(`FORM ${id}: acknowledge must be 'all' or 'data'`);
|
|
@@ -162,6 +163,12 @@ export function formFlow({ id, name, version, description, inputs, after = [], e
|
|
|
162
163
|
if (confirm && typeof confirm === 'object')
|
|
163
164
|
validateRankParameters(id, 'confirm', 'rank_parameters', confirm.rank_parameters);
|
|
164
165
|
else { /* no confirmation */ }
|
|
166
|
+
// confirm.confirmation ('firm' | 'soft', the attribute inputs declare) is definite when declared; a template ('{{…}}')
|
|
167
|
+
// is resolved at run time (an inferred value's descriptor); absent, confirm_soft (confirmationFirmness) decides.
|
|
168
|
+
if (confirm && typeof confirm === 'object' && confirm.confirmation !== undefined && !['firm', 'soft'].includes(confirm.confirmation) && !String(confirm.confirmation).includes('{{'))
|
|
169
|
+
throw new Error(`FORM ${id}: confirm.confirmation must be "firm" or "soft"`);
|
|
170
|
+
else { /* declared, or derived below */ }
|
|
171
|
+
const confirmFirmness = confirm && typeof confirm === 'object' && confirm.confirmation !== undefined ? confirm.confirmation : (confirm_soft === true ? 'soft' : 'firm');
|
|
165
172
|
const incompleteFrom = incomplete_outcome && Object.hasOwn(incomplete_outcome, 'from');
|
|
166
173
|
if (incompleteFrom) {
|
|
167
174
|
if (Object.keys(incomplete_outcome).length !== 1 || typeof incomplete_outcome.from !== 'string' || !/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(incomplete_outcome.from) || !parameters.some(p => p.name === incomplete_outcome.from.split('.')[0] && p.type === 'object'))
|
|
@@ -174,7 +181,8 @@ export function formFlow({ id, name, version, description, inputs, after = [], e
|
|
|
174
181
|
// FLOW parameters are paths only: the question is built here, as a SAY-style template, then passed by name
|
|
175
182
|
SET(`${id}_confirm_q`, 'form_confirm_q', textValue(confirm.question)),
|
|
176
183
|
SET(`${id}_confirm_q_es`, 'form_confirm_q_es', textValue(confirm.question_es || confirm.question)),
|
|
177
|
-
|
|
184
|
+
SET(`${id}_confirm_firmness`, 'form_confirm_firmness', textValue(confirmFirmness)),
|
|
185
|
+
FLOW(`${id}_confirm`, 'confirm-form', 'call', { form: '{{form_instance}}', confirmation: '{{form_confirm_firmness}}', question: '{{form_confirm_q}}', question_es: '{{form_confirm_q_es}}', phrases: '{{form_confirmation.phrases}}', words: '{{form_confirmation.words}}', clarifications: '{{form_confirmation.clarifications}}', no_keys: '{{form_confirmation.no_keys}}', interrupts: '{{form_confirmation.interrupts}}', invites: '{{form_confirmation.invites}}', named_fields: '{{form_confirmation.named_fields}}', reply_context: '{{form_confirmation.reply_context}}', rank_parameters: '{{form_confirmation.rank_parameters}}', exit_context: '{{exit_context}}', exit_context_es: '{{exit_context_es}}' }),
|
|
178
186
|
{ id: `${id}_confirmed`, type: 'CASE', branches: {
|
|
179
187
|
[`condition: (cargo.formConfirm || {})[form_instance] === 'changed'`]: FLOW(`${id}_again`, id, 'replace', { parent_form: '{{parent_form}}', result_input: '{{result_input}}', form_instance: '{{form_instance}}' }),
|
|
180
188
|
// NO: the declined flow takes the form's place, so the action below never runs
|
|
@@ -184,7 +192,7 @@ export function formFlow({ id, name, version, description, inputs, after = [], e
|
|
|
184
192
|
] : [];
|
|
185
193
|
return {
|
|
186
194
|
id, name, version, description, scope, metadata: { formProtocol: 2 }, parameters: [...parameters, { name: 'form_instance', type: 'string', internal: true, description: 'Internal instance to resume' }], ...(primary ? { primary: true } : {}), ...(prompt ? { prompt, prompt_es } : {}),
|
|
187
|
-
variables: vars({ form_on_exhausted: on_exhausted, form_on_no: on_no, form_must_complete: must_complete, form_incomplete_outcome: incompleteFrom ? null : incomplete_outcome, form_instance: '', form_internal_resume: false, form_resume_owner: '', form_arguments: {}, form_exhaustion: null, form_values: {}, form_validation: '', initial_readings: [], inputs: resolveFormInputs(inputs, FIELD_PRESETS), parent_form: '', result_input: '', questions: [], provides: null, exit_context: '', exit_context_es: '', form_confirm_q: '', form_confirm_q_es: '', form_confirmation: confirm || {}, form_should_confirm: false, ...extraVars }),
|
|
195
|
+
variables: vars({ form_on_exhausted: on_exhausted, form_on_no: on_no, form_must_complete: must_complete, form_incomplete_outcome: incompleteFrom ? null : incomplete_outcome, form_instance: '', form_internal_resume: false, form_resume_owner: '', form_arguments: {}, form_exhaustion: null, form_values: {}, form_validation: '', initial_readings: [], inputs: resolveFormInputs(inputs, FIELD_PRESETS), parent_form: '', result_input: '', questions: [], provides: null, exit_context: '', exit_context_es: '', form_confirm_q: '', form_confirm_q_es: '', form_confirm_firmness: '', form_confirmation: confirm || {}, form_should_confirm: false, ...extraVars }),
|
|
188
196
|
steps: [
|
|
189
197
|
...contextualResponseRuntimeSteps(id, [], hasFormResponse(inputs) ? [CONTEXTUAL_RESPONSES_REQUIREMENT] : []),
|
|
190
198
|
...(hasFormResponse(inputs) ? [{ ...SET(`${id}_response_reader_required`, 'noop', "typeof validateFormResponse === 'function'"), requireTrue: true }] : []),
|
|
@@ -684,12 +692,14 @@ const disambiguate = {
|
|
|
684
692
|
// ── confirm-form: the exact confirmation of a finished form; a correction is matched against the form chain ───────
|
|
685
693
|
const confirmForm = {
|
|
686
694
|
id: 'confirm-form', name: 'ConfirmForm', version: '1.0.0', scope: 'isolated',
|
|
687
|
-
description: "Worker: ask a form's confirmation (YES / NO, keys 1 and 2; firm when an action it authorizes is
|
|
695
|
+
description: "Worker: ask a form's confirmation (YES / NO, keys 1 and 2; firm only when an action it authorizes is a payment; otherwise soft: YES is read as a soft confirmation's and offered to the reply ranker). A correction instead ('no, make it $40') is read against the form chain and stored, and the result is 'changed' so the form runs again; an answer with words it cannot read goes to the reply ranker (read-confirm-reply); an unclear one is asked again, and after two the give-up worker takes over. Result: cargo.formConfirm[form] = 'yes' | 'no' | 'changed'. Params: form, question(_es), exit_context(_es).",
|
|
688
696
|
parameters: ['form', 'confirmation', 'question', 'question_es', 'exit_context', 'exit_context_es', 'phrases', 'words', 'interrupts', 'no_keys', 'clarifications', 'invites', 'rank_parameters'].map((n) => ({ name: n, type: ['phrases', 'words', 'interrupts', 'no_keys', 'clarifications', 'invites', 'rank_parameters'].includes(n) ? 'array' : 'string', description: n })).concat([{ name: 'named_fields', type: 'boolean', description: "true: an answer that names one of the form's inputs (its names) without a value re-asks only that input" }]),
|
|
689
697
|
variables: vars({ confirmation: '', cf_clarify: null, named_fields: false, reply_context: '', rank_parameters: [], cf_must: false, cf_presets: FIELD_PRESETS, cf_chain: [], cf_m: null, cf_current: null, cf_result: '', cf_prompt: '', cf_prompt_es: '', cf_words: 0, cf_tries: 0, phrases: null, words: [], clarifications: [], no_keys: ['2'], interrupts: [], invites: [], cf_choices: [], cf_event: '', cf_amount: false, cf_questions: [], cf_provides: null, cf_skip_gate: '' }),
|
|
690
698
|
steps: [
|
|
691
699
|
SIDE('cf_reset', "(cargo.formConfirm = cargo.formConfirm || {}, cargo.formConfirm[form] = '', cargo.confirmRanked = '', true)"),
|
|
692
|
-
|
|
700
|
+
// YES matches only as the whole answer, firm or soft (as an input confirmation's): soft widens the words and offers the
|
|
701
|
+
// ranker YES; the flow's own YES phrases inside a longer sentence ("quiero cancelar el club, no la cuenta") never confirm.
|
|
702
|
+
SET('cf_choices', 'cf_choices', `[{key:'yes',phrases:['1'].concat(Array.isArray(phrases)?phrases:Array.isArray(words)?words:[]).concat(confirmation==='soft'?${JSON.stringify(ANSWER_WORDS.yes)}:[]),exact:true},{key:'no',phrases:(Array.isArray(no_keys)?no_keys:['2']).concat(${JSON.stringify(ANSWER_WORDS.no)}),exact:true}].concat((Array.isArray(interrupts)?interrupts:[]).map(function(event){return {key:'event:'+event.key,phrases:event.phrases ?? event.words,exact:event.exact!==false};}))`),
|
|
693
703
|
SET('cf_initial_chain', 'cf_chain', CHAIN_JS),
|
|
694
704
|
SET('cf_amount_context', 'cf_amount', "cf_chain.some(function(input){return input.profile && input.profile.kind==='amount';})"),
|
|
695
705
|
SET('cf_prompt', 'cf_prompt', "formInputPrompt(cargo,form,'__confirm',{question:question,question_es:question_es},{},'en')"),
|
|
@@ -752,7 +762,7 @@ const confirmInputProposal = formFlow({ id: 'confirm-input-proposal', name: 'Con
|
|
|
752
762
|
{ ...SIDE('proposal_bind', '(bindFormProposal(cargo,parent_form,input,form_instance),true)'), requireTrue: true },
|
|
753
763
|
SET('proposal_confirmation', 'form_confirmation', 'Object.assign({},proposal.descriptor)'),
|
|
754
764
|
],
|
|
755
|
-
confirm: { question: '{{proposal.descriptor.question}}', question_es: '{{proposal.descriptor.question_es || proposal.descriptor.question}}' },
|
|
765
|
+
confirm: { question: '{{proposal.descriptor.question}}', question_es: '{{proposal.descriptor.question_es || proposal.descriptor.question}}', confirmation: "{{proposal.descriptor.confirmation || 'soft'}}" },
|
|
756
766
|
after: [{ ...SIDE('proposal_confirmed', "decideFormProposal(cargo,parent_form,input,form_instance,'confirmed')"), requireTrue: true }],
|
|
757
767
|
declined: [NOOP('proposal_declined_marker')],
|
|
758
768
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proposal-contract.d.ts","sourceRoot":"","sources":["../../src/helpers/proposal-contract.js"],"names":[],"mappings":"AAMA,uEAcC;
|
|
1
|
+
{"version":3,"file":"proposal-contract.d.ts","sourceRoot":"","sources":["../../src/helpers/proposal-contract.js"],"names":[],"mappings":"AAMA,uEAcC;AAWD,4DAWC;AACD,gEAIC;AACD,iGAAiG;AACjG,+EASC;AA1DM,+CAAoC"}
|
|
@@ -26,7 +26,7 @@ export function validateProposalData(value, seen = new Set()) {
|
|
|
26
26
|
seen.delete(value);
|
|
27
27
|
return value;
|
|
28
28
|
}
|
|
29
|
-
const confirmationKeys = ['question', 'question_es', 'binding', 'phrases', 'words', 'interrupts', 'no_keys', 'clarifications', 'on_declined'];
|
|
29
|
+
const confirmationKeys = ['question', 'question_es', 'confirmation', 'binding', 'phrases', 'words', 'interrupts', 'no_keys', 'clarifications', 'on_declined'];
|
|
30
30
|
function confirmation(value, required) {
|
|
31
31
|
if (required && !string(value.question))
|
|
32
32
|
fail('needs a nonempty question');
|
|
@@ -35,6 +35,8 @@ function confirmation(value, required) {
|
|
|
35
35
|
fail(key + ' must be nonempty text');
|
|
36
36
|
if (value.on_declined !== undefined && !safeName(value.on_declined))
|
|
37
37
|
fail('on_declined needs a static worker id');
|
|
38
|
+
if (value.confirmation !== undefined && !['firm', 'soft'].includes(value.confirmation))
|
|
39
|
+
fail('confirmation must be "firm" or "soft"');
|
|
38
40
|
for (const key of ['phrases', 'words', 'no_keys', 'clarifications'])
|
|
39
41
|
if (value[key] !== undefined && (!Array.isArray(value[key]) || !value[key].every(string)))
|
|
40
42
|
fail(key + ' must be an array of nonempty phrases');
|
package/dist/runtime-identity.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const JSFE_RUNTIME_FINGERPRINT = "sha256:
|
|
1
|
+
export const JSFE_RUNTIME_FINGERPRINT = "sha256:d01edb5b556d1bc3c8825df569b295adf6b359da2293ce9ce009bab01ea7e2b5";
|
|
@@ -339,9 +339,10 @@ it.
|
|
|
339
339
|
Some options are **never offered**, so the ranker cannot pick them: the YES of a firm confirmation (an input's, or a form's
|
|
340
340
|
confirmation of an exact action), a `current_only` choice while another question is asked, and text. `exact` on a choice says how words
|
|
341
341
|
match it, not what it does, so exact choices are offered. A charge or a cancellation is guarded by `confirm: {exact: true}`
|
|
342
|
-
on its action
|
|
343
|
-
|
|
344
|
-
|
|
342
|
+
on its action. A form confirmation is firm only when it authorizes a payment (effect `charge`), unless the form declares
|
|
343
|
+
`confirm.confirmation`: a firm one offers the ranker NO, corrections and a person (a ranker can understand "no thanks,
|
|
344
|
+
maybe later" as NO; it can never produce a YES to a payment). Every other confirmation is soft: its YES is read as a
|
|
345
|
+
soft confirmation's and is offered to the ranker too. Options that cost more when wrong need a higher score (a NO that would cancel what the
|
|
345
346
|
caller was confirming, or a new request that leaves the question unanswered). A reply that clears no option is
|
|
346
347
|
`answers`, and the question is asked again if nothing usable was in it.
|
|
347
348
|
|
|
@@ -410,8 +411,9 @@ Cancellation and Concurrency" explains each rule and the production incident beh
|
|
|
410
411
|
|
|
411
412
|
- **Choices.** A choice has a key, phrases in each language and, on a call, keypad keys. A key matches only as the
|
|
412
413
|
whole answer. Sensitive choices are `exact`.
|
|
413
|
-
- **Confirmations.** `confirmation: "firm"
|
|
414
|
-
|
|
414
|
+
- **Confirmations.** One attribute, `confirmation: "firm" | "soft"`, on all three confirmations: an input's, a form's
|
|
415
|
+
`confirm`, and a proposal's policy. Declared, it is definite. Absent: firm only for a payment, soft otherwise (a
|
|
416
|
+
proposal is soft). Firm: only confirmation words. Soft: the wider YES vocabulary, and the reply ranker may pick YES.
|
|
415
417
|
- **Proposals.** When the flow infers a value ("Is this the card ending in 4242?"), a proposal asks the caller to
|
|
416
418
|
approve that one value. It never authorizes the final action.
|
|
417
419
|
- **Questions in the middle of a task.** "When is it due?" asked while paying is answered from the provider's catalog,
|
|
@@ -214,7 +214,7 @@ Declarations:
|
|
|
214
214
|
|
|
215
215
|
| Declaration | Meaning |
|
|
216
216
|
| --- | --- |
|
|
217
|
-
|
|
|
217
|
+
| `confirmation:"firm"` / `"soft"` (an input, a form's `confirm`, a proposal policy) | A yes/no decision; declared, it is definite; absent, firm only for a payment. Firm: YES only by `CONFIRM_WORDS` or the option's own exact phrases. Soft (an offer): the wider YES vocabulary. NO by the NO vocabulary or the option's own phrases. |
|
|
218
218
|
| input `invites:[names]`, `form.confirm.invites` | The other inputs this question (or the read-back) also asks for ("NEW CARD, LINK, or a different amount" invites `amount`), in its form or an enclosing one; read as in order while it is asked. |
|
|
219
219
|
| profile `out_of_order:{…}` | A narrower signature for out-of-order reading: the payment card takes a bare last four when asked, but out of order only a card word or "ending in 4242". |
|
|
220
220
|
| `authenticate-user` `other_whole_answer:true` | At the code question "another account" switches only as the whole answer; inside a sentence the reply ranker decides, offered `another_account` and `no_other_account` ("I understand. We still need to secure access to your account.", then the question). Default off. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsfe",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.38.0",
|
|
4
4
|
"description": "TypeScript workflow engine for conversational/automation apps: multi-flow orchestration, interrupt/resume, stack-based control, robust errors, and flexible tool/REST integration.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ron Pinkas",
|