jsfe 1.7.0 → 1.7.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.
- package/README.md +11 -1
- package/dist/forms/action-tools.js +1 -1
- package/dist/forms/contracts.js +2 -2
- package/dist/forms/flows.d.ts.map +1 -1
- package/dist/forms/flows.js +45 -22
- package/dist/forms/session-actions.d.ts +14 -0
- package/dist/forms/session-actions.d.ts.map +1 -0
- package/dist/forms/session-actions.js +40 -0
- package/dist/helpers/form-conversation.d.ts +28 -0
- package/dist/helpers/form-conversation.d.ts.map +1 -0
- package/dist/helpers/form-conversation.js +57 -0
- package/dist/helpers/form-state.d.ts +1 -1
- package/dist/helpers/form-state.d.ts.map +1 -1
- package/dist/helpers/form-state.js +3 -1
- package/dist/helpers/index.d.ts +11 -1
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/index.js +3 -1
- package/dist/host/capabilities.d.ts +3 -1
- package/dist/host/capabilities.d.ts.map +1 -1
- package/dist/host/capabilities.js +18 -1
- package/dist/host/flow-recovery.d.ts +19 -0
- package/dist/host/flow-recovery.d.ts.map +1 -0
- package/dist/host/flow-recovery.js +43 -0
- package/dist/host/index.d.ts +3 -1
- package/dist/host/index.d.ts.map +1 -1
- package/dist/host/index.js +2 -1
- package/dist/index.d.ts +20 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +69 -9
- package/dist/runtime-identity.js +1 -1
- package/docs/development-release.md +9 -1
- package/docs/forms.md +30 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -769,12 +769,22 @@ return aiResponse;
|
|
|
769
769
|
is already repaired: the next turn starts with fresh intent detection. Log or record the error;
|
|
770
770
|
no rollback is needed. A deliberate `RETURN ''` (which ends every flow) is not flagged.
|
|
771
771
|
|
|
772
|
+
Native FORM collection in 1.7.2 acknowledges an unreadable answer once, then yields the current
|
|
773
|
+
utterance while retaining its draft. Explicit recovery metadata distinguishes resumption, required
|
|
774
|
+
restart, unknown-action hold and no promised recovery; business completion remains independent.
|
|
775
|
+
See the [retry/yield/recovery contract](docs/forms.md#retry-yield-and-recovery-contract-172).
|
|
776
|
+
|
|
772
777
|
### Host Responsibilities: Tool Calls, Cancellation and Concurrency
|
|
773
778
|
|
|
774
779
|
A flow can take actions in the outside world through `CALL-TOOL` — charge a card, send an SMS,
|
|
775
780
|
create a ticket. For ordinary `CALL-TOOL` flows, **the session returned by `updateActivity` records
|
|
776
781
|
which steps already ran**; the host owns its persistence. Native performers can additionally use an
|
|
777
|
-
explicitly attached durable action journal
|
|
782
|
+
explicitly attached durable action journal (the native default). An explicit
|
|
783
|
+
`engine.actionPersistence = 'session-only'` opt-out retains confirmation and saved session action guards
|
|
784
|
+
without journal storage. It cannot prevent a repeated charge after an effect succeeds and its updated
|
|
785
|
+
session save is lost. The reference host selects it with `jsfe: {actionPersistence:'session-only'}`;
|
|
786
|
+
`durableActions:true` conflicts with that setting. Keep each existing conversation on its original mode.
|
|
787
|
+
Neither policy removes the host responsibilities below.
|
|
778
788
|
Getting these rules wrong can repeat an action the user asked for once.
|
|
779
789
|
|
|
780
790
|
The reference host defaults `config.jsfe` to `{durableActions:false,sessionEncoding:'plain'}`. Durable
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Internal transport ports. Registered by WorkflowEngine; no network implementation lives in JSFE. */
|
|
2
2
|
export const ACTION_TOOLS = ['claim', 'complete'].map(operation => ({
|
|
3
|
-
id: `jsfe:action-${operation}`, name: `JSFE action ${operation}`, description: `Await the configured
|
|
3
|
+
id: `jsfe:action-${operation}`, name: `JSFE action ${operation}`, description: `Await the configured guarded action ${operation} port.`,
|
|
4
4
|
parameters: { type: 'object', properties: { record: { type: 'object' }, action: { type: 'object' }, ...(operation === 'complete' ? { outcome: { type: ['object', 'null'] } } : {}) }, required: ['record', 'action'], additionalProperties: false },
|
|
5
5
|
implementation: { type: 'local', function: operation === 'claim' ? 'jsfeClaimAction' : 'jsfeCompleteAction', retries: 0, timeout: 10000 },
|
|
6
6
|
}));
|
package/dist/forms/contracts.js
CHANGED
|
@@ -114,7 +114,7 @@ export function definePerformer({ performs, contract, prepare, present, project
|
|
|
114
114
|
call('prepare', prepare, { form: '{{form_instance}}' }),
|
|
115
115
|
set('request', 'pf_request', 'cargo.actionRequest'),
|
|
116
116
|
set('select', 'pf_action', 'selectFormAction(cargo, form_instance, performs)'),
|
|
117
|
-
when('bad_request', "!(pf_action === null || pf_action && typeof pf_action === 'object' && pf_action.action) || pf_action !== null && (!pf_request || typeof pf_request !== 'object')", { id: `${spec.id}_prepare_failed`, type: 'FLOW', value: 'give-up', callType: 'reboot', parameters: { context: '{{exit_context}}', context_es: '{{exit_context_es}}' } }),
|
|
117
|
+
when('bad_request', "!(pf_action === null || pf_action && typeof pf_action === 'object' && pf_action.action) || pf_action !== null && (!pf_request || typeof pf_request !== 'object')", { id: `${spec.id}_prepare_failed`, type: 'FLOW', value: 'give-up', callType: 'reboot', recovery: { version: 1, mode: 'none', reason: 'performer_request_invalid' }, parameters: { context: '{{exit_context}}', context_es: '{{exit_context_es}}' } }),
|
|
118
118
|
set('proposal', 'pf_record', "pf_action ? prepareFormAction(cargo, form_instance, pf_action, pf_request) : null"),
|
|
119
119
|
];
|
|
120
120
|
const flow = formFlow({ ...spec, confirm, confirmation_condition: "pf_action !== null && pf_action && typeof pf_action === 'object'",
|
|
@@ -129,7 +129,7 @@ export function definePerformer({ performs, contract, prepare, present, project
|
|
|
129
129
|
{ ...set('receipt_applied', 'noop', 'pf_receipt ? consumeActionReceipt(cargo,pf_receipt) : true'), requireTrue: true },
|
|
130
130
|
call('present', present, { form: '{{form_instance}}', request: '{{pf_request}}', result: '{{pf_result}}' }),
|
|
131
131
|
...(spec.after || [])] });
|
|
132
|
-
return declared(flow, { role: 'performer', ...contract, requires: ['
|
|
132
|
+
return declared(flow, { role: 'performer', ...contract, requires: ['guardedActions', ...normalizeHostRequirements(contract?.requires, spec.id)], actions: 'performs', form: { inputs: 'inputs' }, prepares: prepare, presents: present, projects: 'projection' });
|
|
133
133
|
}
|
|
134
134
|
/** A registered tool gets one generated adapter with explicit args; no dynamic tool-name interpreter is needed. */
|
|
135
135
|
export function operationWorkers(tools) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../../src/forms/flows.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"flows.d.ts","sourceRoot":"","sources":["../../src/forms/flows.js"],"names":[],"mappings":"AAyFA;;;;;;GAMG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4DC;AACD;;;GAGG;AACH;;;;;;EAGC;AAoTD,wHAAwH;AACxH,wEAaC;AACD,4FAA4F;AAC5F;;;EAYC;AACD,sGAAsG;AACtG;;;EAOC;AAtdM,6CAAuG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsoB9G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAOG"}
|
package/dist/forms/flows.js
CHANGED
|
@@ -28,7 +28,10 @@ const FLOW = (id, value, callType = 'call', parameters) => ({ id, type: 'FLOW',
|
|
|
28
28
|
const WHEN = (id, condition, step) => ({ id, type: 'CASE', branches: { [`condition: ${condition}`]: step, default: NOOP(`${id}_skip`) } });
|
|
29
29
|
const vars = (o) => Object.fromEntries(Object.entries(o).map(([k, v]) => [k, { type: Array.isArray(v) ? 'array' : v === null || typeof v === 'object' ? 'object' : typeof v, value: v }]));
|
|
30
30
|
const incompleteAttrs = (form, reason) => ({ outcome: `{{formIncompleteOutcome(cargo,${form},{outcome:'unresolved',reason:${reason}}).outcome}}`, reason: `{{formIncompleteOutcome(cargo,${form},{outcome:'unresolved',reason:${reason}}).reason}}` });
|
|
31
|
-
const
|
|
31
|
+
const recoveryAttrs = (form, reason, failure = false, mode = 'resume') => { const call = `formConversationOutcome(cargo,${form},${reason},${failure},${JSON.stringify(mode)})`; return { outcome: `{{${call}.outcome}}`, reason: `{{${call}.reason}}`, recovery: { version: 1, mode: `{{${call}.recovery.mode}}`, reason: `{{${call}.recovery.reason}}`, form: `{{${call}.recovery.form || ''}}`, flow: `{{${call}.recovery.flow || ''}}` } }; };
|
|
32
|
+
const incompleteDispatch = (id, form, reason) => ({ id, type: 'DISPATCH', ...recoveryAttrs(form, reason, true, 'none') });
|
|
33
|
+
const yieldDispatch = (id, form, reason) => ({ id, type: 'DISPATCH', ...recoveryAttrs(form, reason) });
|
|
34
|
+
const recoveryStamp = (id, form, reason, mode = 'resume') => ({ ...SET(id, 'noop', 'true'), recovery: recoveryAttrs(form, reason, true, mode).recovery });
|
|
32
35
|
const optionalIncomplete = (id, condition, form, reason) => WHEN(id, `(${condition}) && !!formIncompleteOutcome(cargo,${form},{outcome:'unresolved',reason:${reason}}).owner`, incompleteDispatch(id + '_dispatch', form, reason));
|
|
33
36
|
/** A SET value that yields this text: with {{ }} it is a template (as is); without, a JS string literal. */
|
|
34
37
|
export const textValue = (text) => (String(text).includes('{{') ? String(text) : JSON.stringify(String(text)));
|
|
@@ -115,7 +118,7 @@ export function formFlow({ id, name, version, description, inputs, after = [], e
|
|
|
115
118
|
SET(`${id}_open`, 'form_instance', `openFormInstance(cargo, ${JSON.stringify(id)}, inputs, { parent: parent_form || null, input: result_input || '', context:!!parent_form && !result_input, resume: form_instance || (!parent_form ? (cargo.formTasks || {})[${JSON.stringify(id)}] || '' : ''), providers: provides, questions: questions, incomplete_outcome: ${incompleteExpression}, on_exhausted: form_on_exhausted })`),
|
|
116
119
|
SIDE(`${id}_task`, `(!parent_form ? (cargo.formTasks = cargo.formTasks || {}, cargo.formTasks[${JSON.stringify(id)}] = form_instance) : true, true)`),
|
|
117
120
|
SIDE(`${id}_provider_reset`, '(delete cargo.forms[form_instance].providerRequests, delete cargo.forms[form_instance].activeGates, delete cargo.forms[form_instance].providerGateActive, true)'),
|
|
118
|
-
SIDE(`${id}_pass_reset`, '(cargo.forms[form_instance].replays = 0, form_internal_resume ? true : (cargo.forms[form_instance].attempts = {}), true)'),
|
|
121
|
+
SIDE(`${id}_pass_reset`, '(cargo.forms[form_instance].replays = 0, form_internal_resume ? true : (cargo.forms[form_instance].attempts = {},cargo.forms[form_instance].misses={},cargo.forms[form_instance].feedback={}), true)'),
|
|
119
122
|
SET(`${id}_arguments`, 'form_arguments', `rememberFormArguments(cargo.forms[form_instance], currentContext, ${JSON.stringify(parameters.filter(p => !p.internal && !/^internal\b/i.test(p.description || '')).map(p => p.name))}, form_internal_resume)`),
|
|
120
123
|
...parameters.filter(p => !p.internal && !/^internal\b/i.test(p.description || '')).map(p => SET(`${id}_restore_${p.name}`, p.name, `form_internal_resume ? form_arguments[${JSON.stringify(p.name)}] : currentContext[${JSON.stringify(p.name)}]`)),
|
|
121
124
|
...before,
|
|
@@ -137,6 +140,7 @@ export function formFlow({ id, name, version, description, inputs, after = [], e
|
|
|
137
140
|
WHEN(`${id}_resolve_validation_exhausted`, "form_validation === 'failed'", SET(`${id}_validation_exhaustion`, 'form_exhaustion', "exhaustFormInput(cargo,form_instance,((cargo.formCheck || {}).invalid || [])[0] ? cargo.formCheck.invalid[0].name : '')")),
|
|
138
141
|
WHEN(`${id}_recover_validation_exhausted`, 'form_exhaustion', FLOW(`${id}_validation_recovery`, '{{form_exhaustion.flow}}', 'reboot', { form: '{{form_exhaustion.form}}', failed_form: '{{form_exhaustion.failed_form}}', input: '{{form_exhaustion.input}}', failed_input: '{{form_exhaustion.failed_input}}', reason: '{{form_exhaustion.reason}}' })),
|
|
139
142
|
optionalIncomplete(`${id}_optional_validation_failed`, "form_validation === 'failed'", 'form_instance', "form_instance+'_incomplete'"),
|
|
143
|
+
WHEN(`${id}_validation_failed_recovery`, "form_validation === 'failed'", recoveryStamp(`${id}_validation_stamp`, 'form_instance', "'validation_exhausted'", 'none')),
|
|
140
144
|
WHEN(`${id}_validation_failed`, "form_validation === 'failed'", FLOW(`${id}_validation_give_up`, 'give-up', 'reboot', { context: '{{exit_context}}', context_es: '{{exit_context_es}}' })),
|
|
141
145
|
WHEN(`${id}_validation_message`, "form_validation === 'retry' && cargo.formCheck.message", SAY(`${id}_validation_say`, '{{cargo.formCheck.message}}', '{{cargo.formCheck.message_es}}')),
|
|
142
146
|
WHEN(`${id}_validation_retry`, "form_validation === 'retry'", FLOW(`${id}_validate_again`, id, 'replace', { parent_form: '{{parent_form}}', result_input: '{{result_input}}', form_instance: '{{form_instance}}' })),
|
|
@@ -198,26 +202,31 @@ const collectInput = {
|
|
|
198
202
|
id: 'collect-input', name: 'CollectInput', version: '1.0.0', scope: 'isolated',
|
|
199
203
|
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).",
|
|
200
204
|
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 })),
|
|
201
|
-
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_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: '' }),
|
|
205
|
+
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: '' }),
|
|
202
206
|
steps: [
|
|
203
207
|
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; })()"),
|
|
204
208
|
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]"),
|
|
205
|
-
WHEN('ci_no_spec', '!ci_spec', END('ci_no_spec_end',
|
|
209
|
+
WHEN('ci_no_spec', '!ci_spec', END('ci_no_spec_end', recoveryAttrs('form', "'collect_input_spec'", true, 'none'))),
|
|
206
210
|
SET('ci_count_missed', 'ci_missed', "Number(((cargo.forms[form] || {}).attempts || {})[current] || 0)"),
|
|
207
211
|
SET('ci_max_attempts', 'ci_max', 'Number(ci_spec.max_attempts || 3)'),
|
|
212
|
+
SET('ci_context_misses', 'ci_misses', 'Number(((cargo.forms[form] || {}).misses || {})[current] || 0)'),
|
|
213
|
+
SET('ci_context_limit', 'ci_miss_limit', 'Number(ci_spec.max_misses || 2)'),
|
|
214
|
+
WHEN('ci_context_yield', 'ci_misses >= ci_miss_limit', yieldDispatch('ci_misses_dispatch', 'form', "'input_misses_exhausted'")),
|
|
208
215
|
SET('ci_context', 'ci_ctx', "typeof exit_context !== 'undefined' && exit_context ? exit_context : 'your request'"),
|
|
209
216
|
SET('ci_context_es', 'ci_ctx_es', "typeof exit_context_es !== 'undefined' && exit_context_es ? exit_context_es : 'su solicitud'"),
|
|
210
217
|
WHEN('ci_resolve_exhausted', 'ci_missed >= ci_max', SET('ci_exhausted_policy', 'ci_exhaustion', 'exhaustFormInput(cargo,form,current)')),
|
|
211
218
|
WHEN('ci_declared_exhausted', 'ci_exhaustion', FLOW('ci_exhausted_recovery', '{{ci_exhaustion.flow}}', 'reboot', { form: '{{ci_exhaustion.form}}', failed_form: '{{ci_exhaustion.failed_form}}', input: '{{ci_exhaustion.input}}', failed_input: '{{ci_exhaustion.failed_input}}', reason: '{{ci_exhaustion.reason}}' })),
|
|
212
219
|
optionalIncomplete('ci_optional_exhausted', 'ci_missed >= ci_max', 'form', "form+'_incomplete'"),
|
|
220
|
+
WHEN('ci_failure_recovery', 'ci_missed >= ci_max', recoveryStamp('ci_failure_stamp', 'form', "'validation_exhausted'")),
|
|
213
221
|
WHEN('ci_give_up', 'ci_missed >= ci_max', FLOW('ci_give_up_flow', 'give-up', 'reboot', { context: '{{ci_ctx}}', context_es: '{{ci_ctx_es}}' })),
|
|
214
222
|
FLOW('ci_prepare', 'prepare-input', 'call', { form: '{{form}}', input: '{{current}}' }),
|
|
215
223
|
// `refresh`: a flow run before EVERY ask, which computes what the question shows into cargo.forms[form].data
|
|
216
224
|
WHEN('ci_refresh', 'ci_spec.refresh', FLOW('ci_refresh_flow', '{{ci_spec.refresh}}', 'call', { form: '{{form}}', input: '{{current}}' })),
|
|
217
225
|
SET('ci_refetch_spec', 'ci_spec', "(function(){var spec=cargo.forms[form].specs.filter(function(s){return s.name===current;})[0];return spec&&spec.any_of?Object.assign({},spec,{profile:{kind:'group'}}):spec;})()"),
|
|
218
226
|
SET('ci_data', 'ci_d', '(cargo.forms[form] || {}).data || {}'),
|
|
219
|
-
SET('ci_question', 'ci_q', "ci_spec
|
|
220
|
-
SET('ci_question_es', 'ci_q_es', "
|
|
227
|
+
SET('ci_question', 'ci_q', "formInputPrompt(cargo,form,current,ci_spec,ci_d,'en')"),
|
|
228
|
+
SET('ci_question_es', 'ci_q_es', "formInputPrompt(cargo,form,current,ci_spec,ci_d,'es')"),
|
|
229
|
+
SIDE('ci_feedback_consumed', 'consumeFormFeedback(cargo,form,current)'),
|
|
221
230
|
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 : [])"),
|
|
222
231
|
SET('ci_keypad', 'ci_digits', 'ci_spec.digits || null'),
|
|
223
232
|
SET('ci_is_amount', 'ci_amount', "ci_spec.amount === 'marked' ? 'marked' : (ci_spec.amount === true || ci_spec.profile.kind === 'amount')"),
|
|
@@ -236,6 +245,7 @@ const collectInput = {
|
|
|
236
245
|
// The labels and validators ride on each reading, so the presets stay in this one flow.
|
|
237
246
|
SET('ci_enrich', 'ci_m', 'enrichFormMatches(ci_m, ci_chain, form, ci_names, current)'),
|
|
238
247
|
SET('ci_question_key', 'ci_qkey', "ci_m.questions.length ? ci_m.questions[0] : !ci_m.hits.length && !ci_m.ambiguous.length && Array.isArray(questions) && questions.length && provides ? pickChoice(cargo.choiceText, questions, false) : ''"),
|
|
248
|
+
WHEN('ci_understood', 'ci_m.hits.length || ci_m.ambiguous.length || ci_m.questions.length || ci_qkey && ci_qkey.indexOf("__") !== 0', SIDE('ci_clear_misses', 'clearFormMisses(cargo,form,current)')),
|
|
239
249
|
SET('ci_count_words', 'ci_words', "String(cargo.choiceText || '').split(/\\s+/).filter(function (w) { return /[a-záéíóúüñ]/i.test(w); }).length"),
|
|
240
250
|
// Independent field clauses are committed before a provider can suspend on its gate.
|
|
241
251
|
WHEN('ci_question_ambiguity', 'ci_m.questions.length > 0 && ci_m.ambiguous.length > 0', FLOW('ci_question_clarify', 'disambiguate-input', 'call', { readings: '{{ci_m.ambiguous}}', hits: '{{ci_m.hits}}', remaining: '{{ci_m.remaining}}' })),
|
|
@@ -248,8 +258,8 @@ const collectInput = {
|
|
|
248
258
|
'condition: ci_m.hits.length > 0': FLOW('ci_accept', 'collect-input-accept', 'call', { hits: '{{ci_m.hits}}' }),
|
|
249
259
|
"condition: ci_m.questions.length > 0": END('ci_question_handled'),
|
|
250
260
|
"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}}' }),
|
|
251
|
-
'condition: ci_words >= 3':
|
|
252
|
-
default: SIDE('ci_miss', "(cargo
|
|
261
|
+
'condition: ci_words >= 3': yieldDispatch('ci_elsewhere', 'form', "'input_not_matched'"),
|
|
262
|
+
default: SIDE('ci_miss', "(missFormInput(cargo,form,current),true)")
|
|
253
263
|
} },
|
|
254
264
|
],
|
|
255
265
|
};
|
|
@@ -317,7 +327,7 @@ const acceptedEffects = {
|
|
|
317
327
|
SET('ie_first', 'ca_h', 'readings.length ? readings[0] : null'),
|
|
318
328
|
WHEN('ie_none', '!ca_h', END('ie_end')),
|
|
319
329
|
SET('ie_rest', 'ca_rest', 'readings.slice(1)'),
|
|
320
|
-
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, true)")),
|
|
330
|
+
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)")),
|
|
321
331
|
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 + '.') : ''"),
|
|
322
332
|
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 + '.') : ''"),
|
|
323
333
|
WHEN('ie_say', 'ca_ack', SAY('ie_ack', '{{ca_ack}}', '{{ca_ack_es}}')),
|
|
@@ -330,7 +340,7 @@ const disambiguate = {
|
|
|
330
340
|
id: 'disambiguate-input', name: 'DisambiguateInput', version: '1.0.0', scope: 'isolated',
|
|
331
341
|
description: "Worker: the answer fits two or more inputs at the same scope (or a declared confirm_against pair). Stores the unambiguous readings, asks which input the rest was for (keys 1..n, never 8), stores the pick. No pick: nothing stored, not a miss. Params: readings, hits.",
|
|
332
342
|
parameters: [{ name: 'readings', type: 'array', description: 'The ambiguous readings' }, { name: 'hits', type: 'array', description: 'Readings to store as they are' }, { name: 'remaining', type: 'array' }],
|
|
333
|
-
variables: vars({ di_keys: [], di_choices: [], di_q: '', di_q_es: '', di_pick: null, di_value: '', remaining: [], di_next: [], di_rest: [] }),
|
|
343
|
+
variables: vars({ di_form: '', di_input: '', di_prompt: '', di_prompt_es: '', di_words: 0, di_misses: 0, di_keys: [], di_choices: [], di_q: '', di_q_es: '', di_pick: null, di_value: '', remaining: [], di_next: [], di_rest: [] }),
|
|
334
344
|
steps: [
|
|
335
345
|
WHEN('di_store_clear', 'Array.isArray(hits) && hits.length > 0', FLOW('di_store_hits', 'collect-input-accept', 'call', { hits: '{{hits}}' })),
|
|
336
346
|
SET('di_number', 'di_keys', `${KEYS_JS}(readings.length)`),
|
|
@@ -338,8 +348,19 @@ const disambiguate = {
|
|
|
338
348
|
SET('di_said', 'di_value', "readings[0].pii ? '' : String(readings[0].value)"),
|
|
339
349
|
SET('di_question', 'di_q', "(di_value ? 'Is ' + di_value + ' your ' : 'Is that your ') + readings.map(function (r) { return r.label; }).join(' or your ') + '? ' + readings.map(function (r, i) { return (cargo.voice ? 'Press ' + di_keys[i] + ' or say ' : 'Say ') + r.label.toUpperCase(); }).join(', ') + '.'"),
|
|
340
350
|
SET('di_question_es', 'di_q_es', "(di_value ? '¿' + di_value + ' es su ' : '¿Eso es su ') + readings.map(function (r) { return r.label_es; }).join(' o su ') + '? ' + readings.map(function (r, i) { return (cargo.voice ? 'Presione ' + di_keys[i] + ' o diga ' : 'Diga ') + r.label_es.toUpperCase(); }).join(', ') + '.'"),
|
|
341
|
-
|
|
351
|
+
SET('di_owner', 'di_form', 'readings[0].form'),
|
|
352
|
+
SET('di_counter', 'di_input', "'__ambiguity:'+readings.map(function(r){return r.form+':'+r.name;}).sort().join('|')"),
|
|
353
|
+
SET('di_prompt', 'di_prompt', "formInputPrompt(cargo,di_form,di_input,{question:di_q,question_es:di_q_es},{},'en')"),
|
|
354
|
+
SET('di_prompt_es', 'di_prompt_es', "formInputPrompt(cargo,di_form,di_input,{question:di_q,question_es:di_q_es},{},'es')"),
|
|
355
|
+
SIDE('di_feedback_consumed', 'consumeFormFeedback(cargo,di_form,di_input)'),
|
|
356
|
+
FLOW('di_ask', 'ask-choice', 'call', { question: '{{di_prompt}}', question_es: '{{di_prompt_es}}', choices: '{{di_choices}}', accept_other: true, digits: { min: 1, max: 1 } }),
|
|
342
357
|
SET('di_picked', 'di_pick', "/^r\\d+$/.test(String(cargo.choice || '')) ? [readings[Number(String(cargo.choice).slice(1))]] : null"),
|
|
358
|
+
WHEN('di_understood', 'di_pick', SIDE('di_clear_misses', 'clearFormMisses(cargo,di_form,di_input)')),
|
|
359
|
+
SET('di_word_count', 'di_words', "String(cargo.choiceText || '').split(/\\s+/).filter(function(w){return /[a-záéíóúüñ]/i.test(w);}).length"),
|
|
360
|
+
WHEN('di_context', '!di_pick && di_words >= 3', yieldDispatch('di_context_yield', 'di_form', "'ambiguity_not_resolved'")),
|
|
361
|
+
WHEN('di_miss', '!di_pick', SET('di_miss_count', 'di_misses', 'missFormInput(cargo,di_form,di_input)')),
|
|
362
|
+
WHEN('di_exhausted', '!di_pick && di_misses >= 2', yieldDispatch('di_misses_yield', 'di_form', "'ambiguity_misses_exhausted'")),
|
|
363
|
+
WHEN('di_retry', '!di_pick', FLOW('di_retry_question', 'disambiguate-input', 'replace', { readings: '{{readings}}', hits: [], remaining: '{{remaining}}' })),
|
|
343
364
|
WHEN('di_store_pick', 'di_pick', FLOW('di_store_pick_flow', 'collect-input-accept', 'call', { hits: '{{di_pick}}' })),
|
|
344
365
|
SET('di_remaining_first', 'di_next', 'Array.isArray(remaining) && remaining.length ? remaining[0] : []'),
|
|
345
366
|
SET('di_remaining_rest', 'di_rest', 'Array.isArray(remaining) ? remaining.slice(1) : []'),
|
|
@@ -351,13 +372,16 @@ const confirmForm = {
|
|
|
351
372
|
id: 'confirm-form', name: 'ConfirmForm', version: '1.0.0', scope: 'isolated',
|
|
352
373
|
description: "Worker: ask a form's confirmation (exact YES / NO; keys 1 and 2). 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 unclear short answer is asked again, a long unrelated one is DISPATCHed. Result: cargo.formConfirm[form] = 'yes' | 'no' | 'changed'. Params: form, question(_es), exit_context(_es).",
|
|
353
374
|
parameters: ['form', 'question', 'question_es', 'exit_context', 'exit_context_es', 'words', 'interrupts', 'no_keys', 'clarifications'].map((n) => ({ name: n, type: ['words', 'interrupts', 'no_keys', 'clarifications'].includes(n) ? 'array' : 'string', description: n })),
|
|
354
|
-
variables: vars({ cf_presets: FIELD_PRESETS, cf_chain: [], cf_m: null, cf_result: '', cf_words: 0, cf_tries: 0, words: [], clarifications: [], no_keys: ['2'], interrupts: [], cf_choices: [], cf_event: '', cf_amount: false, cf_questions: [], cf_provides: null, cf_skip_gate: '' }),
|
|
375
|
+
variables: vars({ cf_presets: FIELD_PRESETS, cf_chain: [], cf_m: null, cf_result: '', cf_prompt: '', cf_prompt_es: '', cf_words: 0, cf_tries: 0, words: [], clarifications: [], no_keys: ['2'], interrupts: [], cf_choices: [], cf_event: '', cf_amount: false, cf_questions: [], cf_provides: null, cf_skip_gate: '' }),
|
|
355
376
|
steps: [
|
|
356
377
|
SIDE('cf_reset', "(cargo.formConfirm = cargo.formConfirm || {}, cargo.formConfirm[form] = '', true)"),
|
|
357
378
|
SET('cf_choices', 'cf_choices', "[{key:'yes',words:['1'].concat(Array.isArray(words)?words:[]),exact:true},{key:'no',words:Array.isArray(no_keys)?no_keys:['2']}].concat((Array.isArray(interrupts)?interrupts:[]).map(function(event){return {key:'event:'+event.key,words:event.words,exact:event.exact!==false};}))"),
|
|
358
379
|
SET('cf_initial_chain', 'cf_chain', CHAIN_JS),
|
|
359
380
|
SET('cf_amount_context', 'cf_amount', "cf_chain.some(function(input){return input.profile && input.profile.kind==='amount';})"),
|
|
360
|
-
|
|
381
|
+
SET('cf_prompt', 'cf_prompt', "formInputPrompt(cargo,form,'__confirm',{question:question,question_es:question_es},{},'en')"),
|
|
382
|
+
SET('cf_prompt_es', 'cf_prompt_es', "formInputPrompt(cargo,form,'__confirm',{question:question,question_es:question_es},{},'es')"),
|
|
383
|
+
SIDE('cf_feedback_consumed', "consumeFormFeedback(cargo,form,'__confirm')"),
|
|
384
|
+
FLOW('cf_ask', 'ask-choice', 'call', { amount: '{{cf_amount}}', question: '{{cf_prompt}}', question_es: '{{cf_prompt_es}}', choices: '{{cf_choices}}', accept_other: true, reask_long: true, digits: { min: 1, max: 1 }, exit_context: '{{exit_context}}', exit_context_es: '{{exit_context_es}}' }),
|
|
361
385
|
SET('cf_event', 'cf_event', "(Array.isArray(interrupts)?interrupts:[]).filter(function(event){return cargo.choice==='event:'+event.key;}).map(function(event){return event.flow;})[0] || ''"),
|
|
362
386
|
WHEN('cf_interrupt', 'cf_event', FLOW('cf_event_flow', '{{cf_event}}', 'reboot', { form: '{{form}}', event: '{{cargo.choice}}', text: '{{cargo.choiceText}}' })),
|
|
363
387
|
SET('cf_build_chain', 'cf_chain', CHAIN_JS),
|
|
@@ -376,17 +400,16 @@ const confirmForm = {
|
|
|
376
400
|
'condition: cf_m.ambiguous.length > 0': FLOW('cf_ambiguous', 'disambiguate-input', 'call', { readings: '{{cf_m.ambiguous}}', hits: '{{cf_m.hits}}', remaining: '{{cf_m.remaining}}' }),
|
|
377
401
|
'condition: cf_m.hits.length > 0': FLOW('cf_accept', 'collect-input-accept', 'call', { hits: '{{cf_m.hits}}' }),
|
|
378
402
|
"condition: cargo.choice === 'no'": SET('cf_declined', 'cf_result', "'no'"),
|
|
379
|
-
"condition: Array.isArray(clarifications) && pickChoice(String(cargo.choiceText || '').replace(/[?¿]/g,''),[{key:'clarify',words:clarifications,exact:true}],false)==='clarify'": SET('cf_clarification', 'cf_result', "'
|
|
380
|
-
'condition: cf_words >= 3':
|
|
403
|
+
"condition: Array.isArray(clarifications) && pickChoice(String(cargo.choiceText || '').replace(/[?¿]/g,''),[{key:'clarify',words:clarifications,exact:true}],false)==='clarify'": SET('cf_clarification', 'cf_result', "'clarification'"),
|
|
404
|
+
'condition: cf_words >= 3': yieldDispatch('cf_elsewhere', 'form', "'confirmation_not_matched'"),
|
|
381
405
|
default: SET('cf_unclear', 'cf_result', "'unclear'")
|
|
382
406
|
} },
|
|
383
407
|
SET('cf_changed', 'cf_result', "cf_result ? cf_result : 'changed'"),
|
|
384
408
|
// an unclear short answer is asked again (the form runs again and reaches this question), at most twice
|
|
385
|
-
SET('cf_tries_count', 'cf_tries', "cf_result === 'unclear' ? (cargo
|
|
386
|
-
|
|
387
|
-
WHEN('cf_give_up', 'cf_tries > 2', FLOW('cf_give_up_flow', 'give-up', 'reboot', { context: '{{exit_context}}', context_es: '{{exit_context_es}}' })),
|
|
409
|
+
SET('cf_tries_count', 'cf_tries', "cf_result === 'unclear' ? missFormInput(cargo,form,'__confirm') : (clearFormMisses(cargo,form,'__confirm'),0)"),
|
|
410
|
+
WHEN('cf_misses_exhausted', 'cf_tries >= 2', yieldDispatch('cf_misses_dispatch', 'form', "'confirmation_misses_exhausted'")),
|
|
388
411
|
WHEN('cf_bind_revision', "cf_result === 'yes'", SIDE('cf_bind', 'confirmFormRevision(cargo, form)')),
|
|
389
|
-
SIDE('cf_store', "(cargo.formConfirm[form] = cf_result === 'unclear' ? 'changed' : cf_result, true)"),
|
|
412
|
+
SIDE('cf_store', "(cargo.formConfirm[form] = cf_result === 'unclear' || cf_result === 'clarification' ? 'changed' : cf_result, true)"),
|
|
390
413
|
],
|
|
391
414
|
};
|
|
392
415
|
// ── form-done: the result goes to the parent (or cargo.formResults); the form is cleared ─────────────────────────────
|
|
@@ -497,7 +520,7 @@ const provideData = {
|
|
|
497
520
|
parameters: [{ name: 'form', type: 'string', description: 'Originating FORM instance for task-owned deferred questions' }, { name: 'provides', type: 'object', description: 'The provider catalog' }, { name: 'asked', type: 'string', description: 'Fact or set key' }, { name: 'asked_keys', type: 'array', description: 'Independent requested facts or sets' }, { name: 'no_menu', type: 'string', description: "'true': answer only (a question asked inside a form)" }, { name: 'gate_params', type: 'object', description: 'Identifiers the caller already gave, passed to the gate flow as gate_params' }, { name: 'no_gate', type: 'string', description: "'true': the caller is inside a task on the verified account; the gate flow is not run (the verified paths are still checked)" }],
|
|
498
521
|
variables: vars({ form: '', pd_request: null, pd_deferred: null, pd_ready: [], asked_keys: [], no_gate: '', pd_close: false, pd_who: '', no_menu: '', gate_params: null, pd_keys: [], pd_menu: [], pd_mkeys: [], pd_q: '', pd_q_es: '', pd_gated: false, pd_ok: false }),
|
|
499
522
|
steps: [
|
|
500
|
-
WHEN('pd_no_catalog', "!provides || typeof provides !== 'object' || !Array.isArray(provides.facts)", END('pd_no_catalog_end', { outcome: 'unresolved', reason: 'provide_data_catalog' })),
|
|
523
|
+
WHEN('pd_no_catalog', "!provides || typeof provides !== 'object' || !Array.isArray(provides.facts)", END('pd_no_catalog_end', { outcome: 'unresolved', reason: 'provide_data_catalog', recovery: { version: 1, mode: 'none', reason: 'provide_data_catalog' } })),
|
|
501
524
|
SET('pd_resolve', 'pd_keys', "(function () { var requested=Array.isArray(asked_keys)&&asked_keys.length?asked_keys:[typeof asked!=='undefined'?String(asked||''):''];return requested.reduce(function(keys,a){var set=(provides.sets||[]).filter(function(x){return x.key===a;})[0];var facts=set?set.facts:provides.facts.some(function(f){return f.key===a;})?[a]:[];return keys.concat(facts.filter(function(key){return keys.indexOf(key)<0;}));},[]); })()"),
|
|
502
525
|
// Nothing specific asked: offer the sets and facts as a menu (keys 1..n, never 8; at most 7 options shown).
|
|
503
526
|
SET('pd_menu_items', 'pd_menu', "(provides.sets || []).concat(provides.facts).slice(0, 7)"),
|
|
@@ -515,7 +538,7 @@ const provideData = {
|
|
|
515
538
|
SET('pd_deferred', 'pd_deferred', `(${finishProviderRequest.toString()})(cargo,pd_request.owner,provides,pd_keys)`),
|
|
516
539
|
SET('pd_merged_keys', 'pd_keys', 'pd_deferred.keys'),
|
|
517
540
|
SET('pd_gate_check', 'pd_ok', `!pd_gated || [].concat(provides.gate.verified).every(function (p) { return !!${PATH_JS}(p); })`),
|
|
518
|
-
WHEN('pd_not_verified', '!pd_ok', END('pd_not_verified_end', { outcome: 'unresolved', reason: 'provide_data_not_verified' })),
|
|
541
|
+
WHEN('pd_not_verified', '!pd_ok', END('pd_not_verified_end', { outcome: 'unresolved', reason: 'provide_data_not_verified', recovery: { version: 1, mode: 'none', reason: 'provide_data_not_verified' } })),
|
|
519
542
|
WHEN('pd_load', 'provides.source && provides.source.flow', FLOW('pd_load_flow', 'load-source', 'call', { source: '{{provides.source}}' })),
|
|
520
543
|
FLOW('pd_answer', 'answer-facts', 'call', { provides: '{{provides}}', keys: '{{pd_keys}}' }),
|
|
521
544
|
WHEN('pd_deferred_more', 'pd_deferred.remaining.length > 0', FLOW('pd_answer_deferred', 'provide-queued-data', 'call', { form: '{{form}}', requests: '{{pd_deferred.remaining}}' })),
|
|
@@ -584,7 +607,7 @@ const loadSource = {
|
|
|
584
607
|
WHEN('ls_begin', 'ls_stale', SIDE('ls_clear_old', 'beginSourceRefresh(cargo, source)')),
|
|
585
608
|
WHEN('ls_load', 'ls_stale', FLOW('ls_loader', '{{source.flow}}', 'call')),
|
|
586
609
|
SET('ls_loaded', 'ls_have', 'ls_stale ? completeSourceRefresh(cargo, source) : true'),
|
|
587
|
-
WHEN('ls_failed', '!ls_have', FLOW('ls_give_up', 'give-up', 'reboot', { context: 'your request', context_es: 'su solicitud' })),
|
|
610
|
+
WHEN('ls_failed', '!ls_have', { ...FLOW('ls_give_up', 'give-up', 'reboot', { context: 'your request', context_es: 'su solicitud' }), recovery: { version: 1, mode: 'none', reason: 'source_unavailable' } }),
|
|
588
611
|
],
|
|
589
612
|
};
|
|
590
613
|
const answerFacts = {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** beginFormAction has already checked completeness, exact confirmation, gate, revision and ownership. */
|
|
2
|
+
export function claimSessionAction(cargo: any, record: any, action: any): {
|
|
3
|
+
status: string;
|
|
4
|
+
outcome: any;
|
|
5
|
+
} | {
|
|
6
|
+
status: string;
|
|
7
|
+
outcome?: undefined;
|
|
8
|
+
};
|
|
9
|
+
/** Cache the first observed outcome before returning it. No receipt or durable recovery checkpoint is created. */
|
|
10
|
+
export function completeSessionAction(cargo: any, record: any, action: any, outcome: any): {
|
|
11
|
+
status: string;
|
|
12
|
+
outcome: any;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=session-actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-actions.d.ts","sourceRoot":"","sources":["../../src/forms/session-actions.js"],"names":[],"mappings":"AAcA,0GAA0G;AAC1G;;;;;;EAOC;AAED,kHAAkH;AAClH;;;EAUC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Non-durable transport policy. All state belongs to the conversation, never the engine instance. */
|
|
2
|
+
import { formSignature } from '../helpers/form-actions.js';
|
|
3
|
+
const hold = reason => ({ status: 'hold', outcome: { status: 'unknown', reason } });
|
|
4
|
+
function ownedAction(cargo, record, action) {
|
|
5
|
+
if (!record?.key || record.action !== action?.action)
|
|
6
|
+
throw new Error('Session action identity mismatch');
|
|
7
|
+
const owner = Object.values(cargo?.forms || {}).find(form => form.actions?.[action.action]?.key === record.key);
|
|
8
|
+
const owned = owner?.actions[action.action];
|
|
9
|
+
if (!owned || owned.signature !== record.signature || owned.scope !== record.scope ||
|
|
10
|
+
formSignature(owned.request) !== formSignature(record.request))
|
|
11
|
+
throw new Error('Session action ownership mismatch');
|
|
12
|
+
if (owned.durableKey)
|
|
13
|
+
throw new Error('A durable action cannot be migrated to session-only execution');
|
|
14
|
+
return owned;
|
|
15
|
+
}
|
|
16
|
+
/** beginFormAction has already checked completeness, exact confirmation, gate, revision and ownership. */
|
|
17
|
+
export function claimSessionAction(cargo, record, action) {
|
|
18
|
+
const owned = ownedAction(cargo, record, action);
|
|
19
|
+
if (owned.sessionOutcome)
|
|
20
|
+
return { status: 'replay', outcome: structuredClone(owned.sessionOutcome) };
|
|
21
|
+
if (owned.status !== 'attempted' || owned.sessionClaimed)
|
|
22
|
+
return hold('action_result_unresolved');
|
|
23
|
+
owned.sessionClaimed = true;
|
|
24
|
+
cargo.actionJournal[owned.key] = owned;
|
|
25
|
+
return { status: 'execute' };
|
|
26
|
+
}
|
|
27
|
+
/** Cache the first observed outcome before returning it. No receipt or durable recovery checkpoint is created. */
|
|
28
|
+
export function completeSessionAction(cargo, record, action, outcome) {
|
|
29
|
+
const owned = ownedAction(cargo, record, action);
|
|
30
|
+
if (!owned.sessionClaimed)
|
|
31
|
+
throw new Error('Session action completion without claim');
|
|
32
|
+
if (!owned.sessionOutcome) {
|
|
33
|
+
if (!['attempted', 'unknown'].includes(owned.status))
|
|
34
|
+
throw new Error('Session action is not pending');
|
|
35
|
+
owned.sessionOutcome = outcome && Object.hasOwn(action.outcomes || {}, outcome.status)
|
|
36
|
+
? structuredClone(outcome) : { status: 'unknown', reason: 'invalid_action_result' };
|
|
37
|
+
cargo.actionJournal[owned.key] = owned;
|
|
38
|
+
}
|
|
39
|
+
return { status: owned.sessionOutcome.status === 'unknown' ? 'unknown' : 'completed', outcome: structuredClone(owned.sessionOutcome) };
|
|
40
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function clearFormMisses(cargo: any, id: any, input: any): boolean;
|
|
2
|
+
export function missFormInput(cargo: any, id: any, input: any): any;
|
|
3
|
+
/** Explicit retry wins. Otherwise acknowledge only an actual unrecognized answer, once per re-ask. */
|
|
4
|
+
export function formInputPrompt(cargo: any, id: any, input: any, spec: any, data?: {}, language?: string): any;
|
|
5
|
+
export function consumeFormFeedback(cargo: any, id: any, input: any): boolean;
|
|
6
|
+
/** Recovery refers to the actual owning root, and never permits a fresh attempt at an unknown action. */
|
|
7
|
+
export function formConversationOutcome(cargo: any, id: any, reason: any, failure?: boolean, recoveryMode?: string): {
|
|
8
|
+
outcome: any;
|
|
9
|
+
reason: any;
|
|
10
|
+
recovery: {
|
|
11
|
+
version: number;
|
|
12
|
+
mode: string;
|
|
13
|
+
reason: any;
|
|
14
|
+
form?: undefined;
|
|
15
|
+
flow?: undefined;
|
|
16
|
+
};
|
|
17
|
+
} | {
|
|
18
|
+
outcome: any;
|
|
19
|
+
reason: any;
|
|
20
|
+
recovery: {
|
|
21
|
+
version: number;
|
|
22
|
+
mode: string;
|
|
23
|
+
reason: any;
|
|
24
|
+
form: any;
|
|
25
|
+
flow: any;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=form-conversation.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** Shared FORM retry, contextual yield and recovery policy; all state is conversation-owned. */
|
|
2
|
+
import { formIncompleteOutcome } from './form-state.js';
|
|
3
|
+
export function clearFormMisses(cargo, id, input) {
|
|
4
|
+
const state = cargo.forms?.[id];
|
|
5
|
+
if (!state)
|
|
6
|
+
return true;
|
|
7
|
+
state.misses ||= {};
|
|
8
|
+
state.feedback ||= {};
|
|
9
|
+
delete state.misses[input];
|
|
10
|
+
delete state.feedback[input];
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
export function missFormInput(cargo, id, input) {
|
|
14
|
+
const state = cargo.forms?.[id];
|
|
15
|
+
if (!state)
|
|
16
|
+
throw new Error('Cannot record a miss without its owned form');
|
|
17
|
+
state.misses ||= {};
|
|
18
|
+
state.feedback ||= {};
|
|
19
|
+
state.misses[input] = Number(state.misses[input] || 0) + 1;
|
|
20
|
+
state.feedback[input] = 'miss';
|
|
21
|
+
return state.misses[input];
|
|
22
|
+
}
|
|
23
|
+
/** Explicit retry wins. Otherwise acknowledge only an actual unrecognized answer, once per re-ask. */
|
|
24
|
+
export function formInputPrompt(cargo, id, input, spec, data = {}, language = 'en') {
|
|
25
|
+
const es = language === 'es', feedback = cargo.forms?.[id]?.feedback?.[input];
|
|
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
|
+
const retry = es ? spec.retry_es : spec.retry;
|
|
28
|
+
if (feedback && retry)
|
|
29
|
+
return retry;
|
|
30
|
+
return (feedback === 'miss' ? (es ? 'Disculpe, no le entendí. ' : "Sorry, I didn't catch that. ") : '') + question;
|
|
31
|
+
}
|
|
32
|
+
export function consumeFormFeedback(cargo, id, input) { if (cargo.forms?.[id]?.feedback)
|
|
33
|
+
delete cargo.forms[id].feedback[input]; return true; }
|
|
34
|
+
/** Recovery refers to the actual owning root, and never permits a fresh attempt at an unknown action. */
|
|
35
|
+
export function formConversationOutcome(cargo, id, reason, failure = false, recoveryMode = 'resume') {
|
|
36
|
+
const completed = formIncompleteOutcome(cargo, id, { outcome: 'unresolved', reason });
|
|
37
|
+
if (completed.owner && ['resolved', 'deflected'].includes(completed.outcome))
|
|
38
|
+
return { outcome: completed.outcome, reason: completed.reason, recovery: { version: 1, mode: 'none', reason: completed.reason } };
|
|
39
|
+
const seen = new Set();
|
|
40
|
+
let state = cargo.forms?.[id], root = state;
|
|
41
|
+
while (state && !seen.has(state.id)) {
|
|
42
|
+
seen.add(state.id);
|
|
43
|
+
root = state;
|
|
44
|
+
state = cargo.forms?.[state.parent];
|
|
45
|
+
}
|
|
46
|
+
if (!root)
|
|
47
|
+
return { outcome: 'unresolved', reason, recovery: { version: 1, mode: 'none', reason } };
|
|
48
|
+
const belongs = form => { const visited = new Set(); while (form && !visited.has(form.id)) {
|
|
49
|
+
if (form.id === root.id)
|
|
50
|
+
return true;
|
|
51
|
+
visited.add(form.id);
|
|
52
|
+
form = cargo.forms?.[form.parent];
|
|
53
|
+
} return false; };
|
|
54
|
+
const unknown = Object.values(cargo.forms || {}).filter(belongs).some(form => Object.values(form.actions || {}).some(record => ['attempted', 'unknown'].includes(record.status)));
|
|
55
|
+
const mode = unknown ? 'hold' : recoveryMode, why = unknown ? 'action_result_unknown' : completed.owner ? completed.reason : reason;
|
|
56
|
+
return { outcome: unknown ? 'unresolved' : completed.owner ? completed.outcome : failure ? 'unresolved' : 'pending', reason: why, recovery: { version: 1, mode, reason: why, form: root.id, flow: root.definition } };
|
|
57
|
+
}
|
|
@@ -81,7 +81,7 @@ export function formPreparation(cargo: any, id: any, input: any): {
|
|
|
81
81
|
export function queueFormReadings(cargo: any, hits?: any[]): boolean;
|
|
82
82
|
export function pendingFormReadings(cargo: any, id: any): any[];
|
|
83
83
|
export function formWorkLimit(cargo: any, id: any): number;
|
|
84
|
-
export function applyFormValidation(cargo: any, id: any, result: any): "failed" | "
|
|
84
|
+
export function applyFormValidation(cargo: any, id: any, result: any): "failed" | "valid" | "retry";
|
|
85
85
|
/** Invalidation is transitive and independent of declaration order. */
|
|
86
86
|
export function invalidateFormInputs(cargo: any, id: any, changed: any, preserve?: any[], preserveNodes?: any[]): any[];
|
|
87
87
|
/** An intentional retry resets the named inputs as well as all of their dependent state. Unknown actions stay held. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-state.d.ts","sourceRoot":"","sources":["../../src/helpers/form-state.js"],"names":[],"mappings":"AAMA,qDAKC;AAED,oDAEC;AAED,qFAAqF;AACrF,
|
|
1
|
+
{"version":3,"file":"form-state.d.ts","sourceRoot":"","sources":["../../src/helpers/form-state.js"],"names":[],"mappings":"AAMA,qDAKC;AAED,oDAEC;AAED,qFAAqF;AACrF,oEAuBC;AAuCD,2FAEC;AACD,8FAcC;AACD,2DAOC;AACD,uEAuCC;AAED,kGAAkG;AAClG,2GAOC;AACD,0HAA0H;AAC1H,uEAwBC;AACD,8HAA8H;AAC9H;;;;EAWC;AACD,uGAAuG;AACvG;;;;;;;;;;QA8BC;AAED,0FAA0F;AAC1F;;;SAIC;AACD,gGAAgG;AAChG;;;;;;;;;;;EAQC;AAED,sGAAsG;AACtG,2DAIC;AACD;;;GAGG;AACH;;;;;;;SAkBC;AAED,2FAA2F;AAC3F,oGAIC;AACD,gFA4BC;AAED,2GAA2G;AAC3G;;;EASC;AACD,mEAoBC;AACD,mEAGC;AACD,sEAUC;AACD,+EAEC;AACD,qFASC;AACD,oGAoCC;AACD;;;SAKC;AACD,qEAGC;AACD,gEAgBC;AACD,2DAQC;AACD,oGAUC;AAED,uEAAuE;AACvE,wHA+BC;AAED,uHAAuH;AACvH,6EAaC;AACD,mHAAmH;AACnH,iGA4CC;AACD,8DAQC;AACD,8EAeC;AAnjBM,8CAAmE"}
|
|
@@ -35,6 +35,8 @@ export function resolveFormInputs(inputs, presets = {}) {
|
|
|
35
35
|
validateMoneyOptions(input.profile.money);
|
|
36
36
|
}
|
|
37
37
|
validateFormExhaustion(input.on_exhausted);
|
|
38
|
+
if (input.max_misses !== undefined && (!Number.isInteger(input.max_misses) || input.max_misses < 1))
|
|
39
|
+
throw new Error('FORM max_misses must be a positive integer');
|
|
38
40
|
if (input.max_attempts !== undefined && (!Number.isInteger(input.max_attempts) || input.max_attempts < 1))
|
|
39
41
|
throw new Error('FORM max_attempts must be a positive integer');
|
|
40
42
|
if (input.any_of)
|
|
@@ -300,7 +302,7 @@ export function openFormInstance(cargo, definition, specs, { parent = null, inpu
|
|
|
300
302
|
cargo.formSequence = Number(cargo.formSequence || 0) + 1;
|
|
301
303
|
const id = `${definition}:${cargo.formSequence}`;
|
|
302
304
|
cargo.forms[id] = { id, definition, task: owner?.task || task || id, parent, result_input: input,
|
|
303
|
-
on_exhausted: on_exhausted || '', incomplete_outcome: incomplete, context_only: !!context, parent_revision: owner?.revision ?? null, specs, values: {}, accepted: {}, errors: {}, pending: {}, seeds: {}, attempts: {}, prepared: {}, data: {}, arguments: {}, contexts: {}, children: {}, childResults: {}, revision: 0,
|
|
305
|
+
on_exhausted: on_exhausted || '', incomplete_outcome: incomplete, context_only: !!context, parent_revision: owner?.revision ?? null, specs, values: {}, accepted: {}, errors: {}, pending: {}, seeds: {}, attempts: {}, misses: {}, feedback: {}, prepared: {}, data: {}, arguments: {}, contexts: {}, children: {}, childResults: {}, revision: 0,
|
|
304
306
|
providers: providers || owner?.providers || null, questions: questions?.length ? questions : owner?.questions || [], status: 'collecting' };
|
|
305
307
|
if (owner) {
|
|
306
308
|
if (context) {
|
package/dist/helpers/index.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export namespace STANDARD_FUNCTIONS {
|
|
2
|
+
export { clearFormMisses };
|
|
3
|
+
export { missFormInput };
|
|
4
|
+
export { formInputPrompt };
|
|
5
|
+
export { consumeFormFeedback };
|
|
6
|
+
export { formConversationOutcome };
|
|
2
7
|
export { readMoneyAmount };
|
|
3
8
|
export { isMoneyAmount };
|
|
4
9
|
export { moneyIntentAllows };
|
|
@@ -88,6 +93,11 @@ export namespace STANDARD_FUNCTIONS {
|
|
|
88
93
|
export { urlToSpeech };
|
|
89
94
|
export { textWithUrlToSpeech };
|
|
90
95
|
}
|
|
96
|
+
import { clearFormMisses } from './form-conversation.js';
|
|
97
|
+
import { missFormInput } from './form-conversation.js';
|
|
98
|
+
import { formInputPrompt } from './form-conversation.js';
|
|
99
|
+
import { consumeFormFeedback } from './form-conversation.js';
|
|
100
|
+
import { formConversationOutcome } from './form-conversation.js';
|
|
91
101
|
import { readMoneyAmount } from './money.js';
|
|
92
102
|
import { isMoneyAmount } from './money.js';
|
|
93
103
|
import { moneyIntentAllows } from './money.js';
|
|
@@ -181,5 +191,5 @@ import { validatePhone } from './validators.js';
|
|
|
181
191
|
import { validateEmail } from './validators.js';
|
|
182
192
|
import { urlToSpeech } from './url-speech.js';
|
|
183
193
|
import { textWithUrlToSpeech } from './url-speech.js';
|
|
184
|
-
export { readMoneyAmount, isMoneyAmount, moneyIntentAllows, 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, 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, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, closeFormInstance, checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech };
|
|
194
|
+
export { clearFormMisses, missFormInput, formInputPrompt, consumeFormFeedback, formConversationOutcome, readMoneyAmount, isMoneyAmount, moneyIntentAllows, 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, 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, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, closeFormInstance, checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech };
|
|
185
195
|
//# 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAAwG,wBAAwB;8BAAxB,wBAAwB;gCAAxB,wBAAwB;oCAAxB,wBAAwB;wCAAxB,wBAAwB;gCAmB7C,YAAY;8BAAZ,YAAY;kCAAZ,YAAY;qCAAZ,YAAY;gCAlB/D,mBAAmB;8BAiBrB,qBAAqB;6BAEkG,aAAa;8BAAb,aAAa;yBAAb,aAAa;+BAAb,aAAa;+BAAb,aAAa;kCAAb,aAAa;+BAAb,aAAa;+BAAb,aAAa;2BAAb,aAAa;0BACT,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;4BAUtD,mBAAmB;+BAAnB,mBAAmB;iCAAnB,mBAAmB;kCAC6mB,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;mCAAjB,iBAAiB;qCAAjB,iBAAiB;mCAAjB,iBAAiB;oCAAjB,iBAAiB;kCAAjB,iBAAiB;yBACzoB,gBAAgB;gCAAhB,gBAAgB;4BAAhB,gBAAgB;8BAAhB,gBAAgB;+BACzB,iBAAiB;8BAAjB,iBAAiB;8BAAjB,iBAAiB;4BAC7B,iBAAiB;oCAAjB,iBAAiB"}
|
package/dist/helpers/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { clearFormMisses, missFormInput, formInputPrompt, consumeFormFeedback, formConversationOutcome } from './form-conversation.js';
|
|
1
2
|
import { takeChoiceOffer } from './choice-offer.js';
|
|
2
3
|
import { consumeActionReceipt, authorizeFormActionRetry, formSignature, selectFormAction, confirmFormRevision, prepareFormAction, actionConfirmationValid, beginFormAction, finishFormAction, actionGateVerified } from './form-actions.js';
|
|
3
4
|
/**
|
|
@@ -29,9 +30,10 @@ import { resolveFormInputs, formInputApplies, formInputFilled, nextFormInput, fo
|
|
|
29
30
|
import { checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS } from './otp-check.js';
|
|
30
31
|
import { validateDigits, validatePhone, validateEmail } from './validators.js';
|
|
31
32
|
import { urlToSpeech, textWithUrlToSpeech } from './url-speech.js';
|
|
32
|
-
export { readMoneyAmount, isMoneyAmount, moneyIntentAllows, 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, 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, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, closeFormInstance, checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech, };
|
|
33
|
+
export { clearFormMisses, missFormInput, formInputPrompt, consumeFormFeedback, formConversationOutcome, readMoneyAmount, isMoneyAmount, moneyIntentAllows, 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, 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, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, closeFormInstance, checkOtp, maskDestination, validateOTP, OTP_EXPIRY_MS, validateDigits, validatePhone, validateEmail, urlToSpeech, textWithUrlToSpeech, };
|
|
33
34
|
/** The functions flows call by name, ready to spread into a host's approved functions. */
|
|
34
35
|
export const STANDARD_FUNCTIONS = {
|
|
36
|
+
clearFormMisses, missFormInput, formInputPrompt, consumeFormFeedback, formConversationOutcome,
|
|
35
37
|
readMoneyAmount, isMoneyAmount, moneyIntentAllows, validateMoneyOptions, takeChoiceOffer, matchesChoice, isAnswer, isAgentRequest, isOtherRequest, isSpelledFragment, isAmountAnswer, amountInAnswer, pickChoice,
|
|
36
38
|
readEmail, readPhone, readInternationalPhone, readIdentifier, last4, maskEmail, callerTextNumber, textDestination, callerIsInternational,
|
|
37
39
|
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, 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, rejectFormReadings, invalidateFormInputs, acceptFormReadings, formInstanceApplies, closeFormInstance,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/** Copy/normalize declaration data so subsequent caller mutations cannot change admission. */
|
|
2
2
|
export function normalizeHostRequirements(requirements: any, flowId: any): readonly any[];
|
|
3
|
-
/**
|
|
3
|
+
/** Durable is the native default; session-only must be selected explicitly by the host. */
|
|
4
|
+
export function actionPersistenceMode(mode?: string): "durable" | "session-only";
|
|
5
|
+
/** Boolean requirements remain strict. guardedActions additionally accepts the explicit session-only policy. */
|
|
4
6
|
export function assertFlowHostCapabilities(flows: any, capabilities?: {}): boolean;
|
|
5
7
|
/** Declarative flow admission. No engine imports, I/O, or implicit host capability activation. */
|
|
6
8
|
export class JSFEHostCapabilityError extends Error {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/host/capabilities.js"],"names":[],"mappings":"AAWA,8FAA8F;AAC9F,0FASC;AAED,
|
|
1
|
+
{"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/host/capabilities.js"],"names":[],"mappings":"AAWA,8FAA8F;AAC9F,0FASC;AAED,2FAA2F;AAC3F,iFAKC;AAED,gHAAgH;AAChH,mFA0BC;AA1DD,kGAAkG;AAClG;IACG,uDAMC;IAHE,aAA2C;IAC3C,YAA0B;IAC1B,gBAAkC;CAEvC"}
|