jsfe 1.3.1 → 1.3.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 CHANGED
@@ -12,6 +12,7 @@ npm i jsfe
12
12
  - **[JavaScript Flow Engine User Guide](user-guide.md)**
13
13
  - Comprehensive tutorials, examples, and best practices
14
14
  - **[README.md](README.md)** - Technical API reference (this document)
15
+ - **[Flow author reference](docs/copilot-reference.md)** - the engine in one page, for flow authors and AI flow generators (shipped in the package; the Flow Designer's CoPilot reads it from the published version, so change it with the engine)
15
16
  - **[System Flows](docs/system-flows.md)** and **[Shopify Flows](docs/shopify-flows.md)** - the bundled flow and tool libraries in `flows/`, step by step
16
17
 
17
18
  *For detailed tutorials, step-by-step examples, and comprehensive workflow patterns, see the **[User Guide](user-guide.md)**.*
@@ -544,6 +545,11 @@ Two cargo fields connect the engine to a voice channel:
544
545
  - **`cargo.digits`** (set by the engine): when a `SAY-GET` step declares `digits` (for example
545
546
  `{ "min": 6, "max": 6 }`), the engine copies it to `cargo.digits` while the answer is awaited, so
546
547
  the host can configure keypad (DTMF) collection. It is removed once the input is collected.
548
+ On voice, `digits` also shapes how the answer is stored: an answer whose digit count fits the range is
549
+ stored as its digits, taken as heard and never sent to the voice cleaner ("es 41 34 18…" → `413418…`). On a
550
+ one-key question (`max: 1`) only an answer that is exactly one key (a digit, `*` or `#`, punctuation aside)
551
+ becomes that key; any other answer is stored as spoken and never sent to the cleaner, so a digit inside a
552
+ sentence ("I want to pay 1") is never a key press (jsfe ≥ 1.3.1).
547
553
  - **`asSpoken`** (a `SAY-GET` attribute, opt-in): the answer is stored exactly as given, never reduced to
548
554
  its digits and never sent to the voice cleaner. Use it where the words carry meaning the digits lose,
549
555
  such as an amount: without it, "$1" fitting `digits: { min: 1, max: 1 }` is stored as `1`. The value is
@@ -0,0 +1,87 @@
1
+ # JSFE reference for flow authors
2
+
3
+ What a flow author (and the Flow Designer's CoPilot, which reads this file from the published package) needs to know about the engine itself. A host's own platform — its cargo fields, functions and system flows — is described by that host.
4
+
5
+ ## JSFE Architecture
6
+
7
+ JSFE is a stack-of-stacks interpreter that executes flows as a plugin in its host site, mediating between a User and a Conversational AI. Flows can be marked as PRIMARY, in which case their description is forwarded (in a menu-like form) to an intent-detection AI to determine whether the most recent user prompt should trigger execution of one of them.
8
+
9
+ ## Flow Structure
10
+
11
+ A flow is a JSON object with:
12
+ - id (string): unique kebab-case identifier
13
+ - name (string): PascalCase display name
14
+ - version (string): semver
15
+ - description (string): CRITICAL — the ONLY text the intent-detection AI reads to decide activation. Must clearly state what SHOULD trigger this flow AND what SHOULD NOT. Include trigger phrases and explicitly negate similar-but-wrong intents.
16
+ - prompt / prompt_es (string): short UI label (e.g. "Payment"), NOT a trigger phrase. Used for display: "Flow {prompt} completed"
17
+ - primary (boolean): if true, the intent-detection AI can directly activate this flow from user input. If false, the flow is a sub-flow only reachable via FLOW steps from other flows.
18
+ - parameters (array): values the AI extracts from the user's message on activation, e.g. [{ name: "acct_number", type: "string", description: "..." }]
19
+ - enum (string[], optional, string parameters only): the allowed values, e.g. { name: "ticket_type", type: "string", description: "...", enum: ["Fraud", "Cancellation"] }. A reply matching ignoring case becomes the declared spelling; any other value is dropped, so the flow must still ask for it (keep the SAY-GET). Declare one whenever the values are a closed set. Values: non-empty, no leading/trailing spaces, unique ignoring case.
20
+ - variables (object): flow-scoped variables { varName: { type, description, value? } } — local to this flow instance
21
+ - scope (string, optional): "isolated" makes the flow a self-contained worker: a call (or replace) starts it with ONLY its declared variables and the parameters actually passed (an omitted parameter is undefined, never the caller's or a previous call's value), and its variables never reach the caller — return results through cargo. Default "shared": the caller's variables, by reference. Use "isolated" for generic workers called from many flows.
22
+ - steps (array): ordered list of step objects
23
+ - comments (string, optional): NOT a flow property — placed on the response wrapper object only
24
+
25
+ ## Step Types
26
+
27
+ SAY — Outputs the literal string of its VALUE (English) or the respective VALUE_{language-code} (like value_es), which may include {{}} interpolation placeholders that evaluate any valid JavaScript expression. SAY output messages accumulate until the next wait-state (SAY-GET), which will then relay the combined message to the user.
28
+
29
+ SAY-GET — Same as SAY, but immediately returns control to the user to collect the next prompt into its named VARIABLE property. May include a digits constraint for DTMF input { min, max, autoSubmitChars?, autoSubmitMs? }; on voice a fitting answer is stored as its digits, and on a one-key question (max 1) only an answer that is exactly one key counts as that key. asSpoken: true (or a condition on the flow's variables) stores a voice answer exactly as heard: never reduced to its digits and never sent to the AI voice cleanup. Use it ONLY where that cleanup would lose meaning, such as an amount ("$1" must not become the keypad key 1). Leave it off for names, emails, phone, card and account numbers, codes and yes/no answers: the cleanup is what removes filler ("um, it's Ron") and writes spoken emails and numbers correctly.
30
+
31
+ SET — Evaluates the JavaScript expression in its VALUE property and assigns the result to its named VARIABLE. Can also produce side-effects on cargo (session state) via expressions like cargo.accountNumber = acct_number.
32
+
33
+ CASE — Branching mechanism with multiple CONDITION nodes. Each condition key is prefixed with "condition:" followed by a JavaScript expression. Each condition node contains a SINGLE STEP to execute if its expression evaluates to true. A "default" branch is executed if no conditions match. Branch evaluation order matters — first match wins.
34
+
35
+ SWITCH — Similar to CASE, but branching is based on the value of a single VARIABLE that is evaluated once, then matched against multiple case values. Each case node contains a SINGLE STEP.
36
+
37
+ FLOW — A jump/goto-like mechanism that forwards control to another flow using one of three call types:
38
+ 1. call — Equivalent to a typical function call. The callee runs and then returns control to the caller flow's next step. Variable scope is inherited — the child can read and modify the caller's variables.
39
+ 2. replace — Replaces the current stack frame, removing the caller flow from the stack and proceeding at the first step of the target flow. Inherits the same scope as the replaced flow. If the caller itself was a callee, then when the replacing flow terminates it returns to the original caller (stack-of-stacks semantics).
40
+ 3. reboot — Clears the entire stack of stacks and starts the target flow from its first step as a fresh execution context. Only explicitly passed parameters are available; all other state is cleared (except cargo). SAY text queued before the reboot is delivered with the new flow's output; discardSay: true drops it.
41
+ FLOW steps may include a parameters object to pass values: { "retry_flow": "{{retry_flow}}" }.
42
+
43
+ CALL-TOOL — Invokes either a locally defined function (must be in the APPROVED_FUNCTIONS set), or a REST API endpoint. Either must be defined in the toolsRegistry array following the tool definition schema. Includes tool (tool ID), variable (store result), args (template expressions), and an optional onFail node containing a SINGLE STEP to execute if the tool call fails.
44
+
45
+ RETURN — Unconditionally terminates execution of the entire stack of stacks (full reset), returning control to the JSFE host. The result of evaluating its VALUE expression is returned to the host. If that expression evaluates to '' (empty string), the host will proceed to process the last user prompt that was forwarded to JSFE as input — effectively forwarding to the host AI/RAG. SAY text queued before a RETURN is NOT delivered unless keepSay: true, which delivers it ahead of the value.
46
+
47
+ END — Returns from the current flow only, resuming its caller at the step after the FLOW call — a functional return, with no value (a callee shares its caller's variables unless it is isolated; results travel through cargo). In a flow nobody called, it simply ends the flow.
48
+
49
+ DISPATCH — Ends every flow (all stacks, and any SAY output not yet delivered) and routes the user's input for THIS turn through intent detection, exactly as if no flow had been active: a matching flow starts in the same turn, otherwise the host AI/RAG answers. Takes an id and optionally outcome/reason (no value): the ended flows' outcome reaches the host only when no flow matches. Use it right after a SAY-GET whose answer may really be a new request — e.g. the "default" branch of a menu's CASE, or the answer to "Anything else?" — so the user can move from flow to flow.
50
+
51
+ Declared outcome — the step that ends a flow (RETURN, END, DISPATCH or a final SAY) may carry outcome and reason (both accept {{}}): "unresolved" (the user's need was not met; the flow's transaction is marked failed), "deflected" (a decoy flow absorbed a false-positive intent by design), "resolved" (informational). An outcome that interpolates to empty declares nothing. The host reads it to recover from an unresolved ending.
52
+
53
+ ## Cargo — Session-Persistent State
54
+
55
+ cargo is a session-level object that persists across ALL flows within a chat session. Unlike flow variables which reset on each flow activation, cargo survives flow boundaries, reboots, and returns.
56
+
57
+ Cargo side-effect pattern — use SET with a dummy variable:
58
+ { "type": "SET", "variable": "side_effect", "value": "cargo.accountNumber = acct_number" }
59
+
60
+ Multiple assignments MUST use comma operator, NEVER semicolons (semicolons terminate the expression):
61
+ { "type": "SET", "variable": "side_effect", "value": "cargo.support_context = 'payment', cargo.support_context_es = 'pago'" }
62
+
63
+ ## Variables vs Cargo
64
+
65
+ - Flow variables are LOCAL to the flow instance, BUT:
66
+ - Can be populated via parameters when activated by AI or called by another flow
67
+ - Are inherited by child flows called with callType "call"
68
+ - Set by SET or SAY-GET in PRECEDING steps of the current flow
69
+ - cargo is SESSION-GLOBAL — always available, persists across all flow boundaries
70
+ - When a value could come from either source, use: parameterName || cargo.sameName
71
+ Example: "value": "acct_number || cargo.accountNumber"
72
+
73
+ ## Best Practices
74
+
75
+ 1. JSFE is explicitly designed to promote re-use of small worker flows as sub-flows via call/replace/reboot FLOW steps. Prefer composing small, focused flows over building large monolithic ones.
76
+ 2. JSFE intentionally restricts logical branches (CASE, SWITCH) and onFail handlers to a SINGLE STEP, to encourage composition via named sub-flows instead of long, unnamed step lists.
77
+ 3. Flows advance to the next STEP unconditionally until the end of the flow; therefore, the only valid ways to terminate a flow early are: an END step (back to the caller), a FLOW step with replace or reboot callType, a RETURN step, or a DISPATCH step.
78
+ 4. Before asking the user for information, check if it's already available in cargo from a prior interaction. Use CASE at the top of the flow to detect and skip redundant collection.
79
+ 5. After collecting key info, store it in cargo via SET side-effects so subsequent flows don't re-ask.
80
+ 6. A menu key counts only as the whole answer: compare it exactly (answer.trim() === '1'), never test for a digit inside the answer ("I want to pay 1" is not a key press). On voice, give a one-key question digits { min: 1, max: 1 } and the engine does the same.
81
+ 7. FLOW steps must reference flows that EXIST. If your flow needs a sub-flow that doesn't exist yet, include it in your response.
82
+
83
+ ## Patterns to Avoid
84
+
85
+ 1. SAY in an onFail handler — An onFail with a SAY step will display the error message but then continue to the next step, typically leading to unintended behavior. Use FLOW with reboot to a handler, or RETURN.
86
+ 2. SAY in a CASE/SWITCH branch — A branch whose only step is SAY will continue execution into the following step. If the flow should terminate at that point, follow it with END (back to the caller) or use RETURN (with a language-aware expression), or FLOW with reboot.
87
+ 3. Referencing non-existent flows — Every FLOW step value must be a flow ID that exists. Return an array of flows if new sub-flows are needed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsfe",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "TypeScript workflow engine for conversational/automation apps: multi-flow orchestration, interrupt/resume, stack-based control, robust errors, and flexible tool/REST integration.",
5
5
  "license": "MIT",
6
6
  "author": "Ron Pinkas",
@@ -26,7 +26,8 @@
26
26
  "files": [
27
27
  "dist",
28
28
  "README.md",
29
- "LICENSE"
29
+ "LICENSE",
30
+ "docs/copilot-reference.md"
30
31
  ],
31
32
  "scripts": {
32
33
  "build": "tsc -p tsconfig.json",