@henry-dev/botkit 0.0.0-stage → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -3
- package/dist/index.cjs +7 -0
- package/dist/index.d.cts +749 -0
- package/dist/index.d.ts +749 -0
- package/dist/index.js +7 -0
- package/package.json +49 -5
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
function le(s){return s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var ue=s=>/[\{\}\[\]\(\)\|\+\?\^\$]/.test(s);function Y(s,e){let n=o=>{if(e?.normalize===!1)return o.trim().toLowerCase();let i=o.trim().toLowerCase();return i=i.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()?"'’]/g,""),i=i.replace(/(.)\1{2,}/g,"$1"),i=i.replace(/\s+/g," "),i.trim()},t=o=>{let i=n(o),u=ue(i),f=u?i:le(i);return(e?.boundary??!0)&&!u?`\\b${f}\\b`:f},r=s.filter(o=>o!=="*"),a=s.includes("*"),l=r.map(o=>{let i=t(o);return{pattern:o,regex:new RegExp(i,"i")}}),h=r.length>0?new RegExp(r.map(t).join("|"),"i"):null,d=e?.excludeIf||[],c=d.length>0?new RegExp(d.map(t).join("|"),"i"):null;return{patterns:s,normalize:n,match:o=>{let i=n(o);if(c&&c.test(i))return{matched:!1,pattern:"",confidence:0};if(h&&h.test(i)){for(let u of l)if(u.regex.test(i))return{matched:!0,pattern:u.pattern,confidence:1}}return a?{matched:!0,pattern:"*",confidence:.1}:{matched:!1,pattern:"",confidence:0}}}}function _(s,e){return Y(s,e)}var w=class extends Error{constructor(e){super(e),this.name="CompileError"}},O=class{constructor(e,n,t){this.bot=e;this.id=n;t&&(t.execute&&(this._actionFn=t.execute),t.timeoutMs&&(this._timeoutMs=t.timeoutMs),typeof t.onSuccess=="string"?this._onSuccess={targetNodeId:t.onSuccess}:t.onSuccess&&(this._onSuccess=t.onSuccess),typeof t.onFailure=="string"?this._onFailure={targetNodeId:t.onFailure}:t.onFailure&&(this._onFailure=t.onFailure),typeof t.onTimeout=="string"?this._onTimeout={targetNodeId:t.onTimeout}:t.onTimeout&&(this._onTimeout=t.onTimeout))}bot;id;_actionFn;_timeoutMs=5e3;_onSuccess;_onFailure;_onTimeout;_onEnter;_onExit;_text="";execute(e){return this._actionFn=e,this}timeout(e){return this._timeoutMs=e,this}text(e){return this._text=e,this}onSuccess(e,n){return this._onSuccess={targetNodeId:e,handler:n},this}onFailure(e,n){return this._onFailure={targetNodeId:e,handler:n},this}onTimeout(e,n){return this._onTimeout={targetNodeId:e,handler:n},this}onEnter(e){return this._onEnter=e,this}onExit(e){return this._onExit=e,this}ask(e){return this.bot.ask(e)}action(e,n){return this.bot.action(e,n)}handoff(e,n){return this.bot.handoff(e,n)}global(e,n){return this.bot.global(e,n)}getId(){return this.id}getText(){return this._text}getActionFn(){return this._actionFn}getTimeoutMs(){return this._timeoutMs}getOnSuccess(){return this._onSuccess}getOnFailure(){return this._onFailure}getOnTimeout(){return this._onTimeout}getOnEnterFn(){return this._onEnter}getOnExitFn(){return this._onExit}},A=class{constructor(e,n,t){this.bot=e;this.id=n;t&&(t.reason&&(this._reason=t.reason),t.timeoutMs!==void 0&&(this._timeoutMs=t.timeoutMs),t.resumeNodeId&&(this._resumeNodeId=t.resumeNodeId),t.fallbackNodeId&&(this._fallbackNodeId=t.fallbackNodeId),t.message&&(this._text=t.message),t.queueMessage!==void 0&&(this._queueMessage=t.queueMessage),t.metadata&&(this._metadata=t.metadata))}bot;id;_reason;_timeoutMs=600*1e3;_resumeNodeId;_fallbackNodeId;_text="Connecting you with an agent. Please hold...";_queueMessage="You are in the queue. An agent will be with you shortly.";_metadata;_onEnter;_onExit;_ignoreGlobals;reason(e){return this._reason=e,this}timeout(e){return this._timeoutMs=e,this}resumeAt(e){return this._resumeNodeId=e,this}fallbackTo(e){return this._fallbackNodeId=e,this}message(e){return this._text=e,this}queueMessage(e){return this._queueMessage=e,this}metadata(e){return this._metadata=e,this}onEnter(e){return this._onEnter=e,this}onExit(e){return this._onExit=e,this}ignoreGlobals(e=!0){return this._ignoreGlobals=e,this}ask(e){return this.bot.ask(e)}action(e,n){return this.bot.action(e,n)}handoff(e,n){return this.bot.handoff(e,n)}global(e,n){return this.bot.global(e,n)}getId(){return this.id}getText(){return this._text}getReason(){return this._reason}getTimeoutMs(){return this._timeoutMs}getResumeNodeId(){return this._resumeNodeId}getFallbackNodeId(){return this._fallbackNodeId}getQueueMessage(){return this._queueMessage}getMetadata(){return this._metadata}getOnEnterFn(){return this._onEnter}getOnExitFn(){return this._onExit}getIgnoreGlobals(){return this._ignoreGlobals}},$=class{constructor(e,n,t){this.bot=e;typeof n=="object"&&n!==null&&"match"in n&&typeof n.match=="function"?(this._matcher=n,this._patterns=[...this._matcher.patterns]):typeof n=="string"?this._patterns=[n]:Array.isArray(n)?this._patterns=[...n]:this._patterns=[],typeof t=="string"?this._targetNodeId=t:t&&(t.id&&(this._id=t.id),t.target&&(this._targetNodeId=t.target),t.targetNodeId&&(this._targetNodeId=t.targetNodeId),t.priority&&(this._priority=t.priority),t.response&&(this._responseFn=t.response),t.matcherOptions&&(this._opts=t.matcherOptions))}bot;_id;_targetNodeId;_patterns=[];_opts;_matcher;_priority="high";_responseFn;id(e){return this._id=e,this}then(e){return this._targetNodeId=e,this}response(e){return this._responseFn=e,this}priority(e){return this._priority=e,this}getId(e){return this._id??e}getTargetNodeId(){return this._targetNodeId}getPatterns(){return this._patterns}getOpts(){return this._opts}getMatcher(){return this._matcher}getPriority(){return this._priority}getResponseFn(){return this._responseFn}ask(e){return this.bot.ask(e)}action(e,n){return this.bot.action(e,n)}handoff(e,n){return this.bot.handoff(e,n)}global(e,n){return this.bot.global(e,n)}},P=class{constructor(e,n,t){this.bot=e;this.defaultId=n;this.text=t}bot;defaultId;text;_customId;_branches=[];_responseFn;_fallbackText="Please try again.";_maxRetries=3;_onMaxRetriesFn;_onEnter;_onExit;_ignoreGlobals;getId(){return this._customId??this.defaultId}id(e){return this._customId=e,this}expect(e,n){let t,r;return typeof e=="object"&&e!==null&&"match"in e&&typeof e.match=="function"?(r=e,t=[...r.patterns]):typeof e=="string"?t=[e]:Array.isArray(e)?t=[...e]:t=[],this._branches.push({patterns:t,opts:n,matcher:r}),this}then(e){if(this._branches.length===0)throw new Error(`Cannot call then("${e}") before expect() on node "${this.getId()}"`);let n=this._branches[this._branches.length-1];return n.targetNodeId=e,this}response(e){return this._responseFn=e,this}fallback(e){return this._fallbackText=e,this}retries(e){return this._maxRetries=e,this}onMaxRetries(e){return this._onMaxRetriesFn=e,this}onEnter(e){return this._onEnter=e,this}onExit(e){return this._onExit=e,this}ignoreGlobals(e=!0){return this._ignoreGlobals=e,this}ask(e){return this.bot.ask(e)}action(e,n){return this.bot.action(e,n)}handoff(e,n){return this.bot.handoff(e,n)}global(e,n){return this.bot.global(e,n)}getBranches(){return this._branches}getResponseFn(){return this._responseFn}getFallbackText(){return this._fallbackText}getMaxRetries(){return this._maxRetries}getOnMaxRetriesFn(){return this._onMaxRetriesFn}getOnEnterFn(){return this._onEnter}getOnExitFn(){return this._onExit}getIgnoreGlobals(){return this._ignoreGlobals}},H=class{constructor(e,n=1){this.flowId=e;this.version=n}flowId;version;builders=[];actionBuilders=[];handoffBuilders=[];globalBuilders=[];ask(e){let n=`node_${this.builders.length}`,t=new P(this,n,e);return this.builders.push(t),t}action(e,n){let t=new O(this,e,n);return this.actionBuilders.push(t),t}handoff(e,n){let t=new A(this,e,n);return this.handoffBuilders.push(t),t}global(e,n){let t=new $(this,e,n);return this.globalBuilders.push(t),t}escalate(e){e.set("escalated",!0)}compile(){let e=new Set,n=[];for(let o of this.builders){let i=o.getId();if(e.has(i))throw new w(`Duplicate node id "${i}".`);e.add(i),n.push(i)}for(let o of this.actionBuilders){let i=o.getId();if(e.has(i))throw new w(`Duplicate node id "${i}".`);e.add(i),n.push(i)}for(let o of this.handoffBuilders){let i=o.getId();if(e.has(i))throw new w(`Duplicate node id "${i}".`);e.add(i),n.push(i)}let t=new Set,r=this.globalBuilders.map((o,i)=>{let u=o.getId(`global_${i}`);if(t.has(u))throw new w(`Duplicate global transition id "${u}".`);t.add(u);let f=o.getTargetNodeId();if(!f)throw new w(`Global transition "${u}" for pattern(s) [${o.getPatterns().join(", ")}] is missing an explicit .then() target.`);if(!e.has(f))throw new w(`Unknown node "${f}" referenced by global transition "${u}". Known node IDs: ${n.join(", ")}.`);let p=o.getMatcher()??_(o.getPatterns(),o.getOpts());return{id:u,matcher:p,patterns:o.getPatterns(),targetNodeId:f,priority:o.getPriority(),responseFn:o.getResponseFn()}}),a=this.builders.map((o,i)=>{let u=o.getBranches();if(u.length===0)throw new w(`Node "${o.getId()}" is missing an expect() matcher`);if(u.length>1){for(let x of u)if(!x.targetNodeId)throw new w(`branch for pattern(s) [${x.patterns.join(", ")}] is missing an explicit .then() target \u2014 required once a node has multiple branches`)}let f=i<this.builders.length-1?n[i+1]:void 0,p=[],m=[],g=[],y=[];for(let x of u){let v=x.targetNodeId;if(!v&&u.length===1&&f&&(v=f),v&&!e.has(v))throw new w(`Unknown node "${v}" referenced by transition from node "${o.getId()}". Known node IDs: ${n.join(", ")}.`);p.push(...x.patterns),m.push({targetNodeId:v??"",patterns:x.patterns});let N=x.matcher??_(x.patterns,x.opts);x.patterns.includes("*")?y.push(N):g.push(N)}let b={patterns:p,normalize:x=>x.trim().toLowerCase(),match:x=>{for(let v of g){let N=v.match(x);if(N.matched)return N}for(let v of y){let N=v.match(x);if(N.matched)return N}return{matched:!1,pattern:"",confidence:0}}};return{id:o.getId(),text:o.text,matcher:b,transitions:m,fallbackText:o.getFallbackText(),maxRetries:o.getMaxRetries(),onMaxRetriesFn:o.getOnMaxRetriesFn(),responseFn:o.getResponseFn(),onEnter:o.getOnEnterFn(),onExit:o.getOnExitFn(),ignoreGlobals:o.getIgnoreGlobals()}}),l=this.actionBuilders.map(o=>{let i=o.getActionFn();if(!i)throw new w(`Action node "${o.getId()}" is missing an execute() handler.`);let u=o.getOnSuccess(),f=o.getOnFailure(),p=o.getOnTimeout();if(!u&&!f)throw new w(`Action node "${o.getId()}" must define at least an onSuccess or onFailure transition target.`);if(u&&!e.has(u.targetNodeId))throw new w(`Unknown node "${u.targetNodeId}" referenced by onSuccess transition on action node "${o.getId()}". Known node IDs: ${n.join(", ")}.`);if(f&&!e.has(f.targetNodeId))throw new w(`Unknown node "${f.targetNodeId}" referenced by onFailure transition on action node "${o.getId()}". Known node IDs: ${n.join(", ")}.`);if(p&&!e.has(p.targetNodeId))throw new w(`Unknown node "${p.targetNodeId}" referenced by onTimeout transition on action node "${o.getId()}". Known node IDs: ${n.join(", ")}.`);let m=[];return u&&m.push({targetNodeId:u.targetNodeId,patterns:["success"]}),f&&m.push({targetNodeId:f.targetNodeId,patterns:["failure"]}),p&&m.push({targetNodeId:p.targetNodeId,patterns:["timeout"]}),{id:o.getId(),text:o.getText(),matcher:{patterns:["*"],normalize:g=>g,match:()=>({matched:!0,pattern:"*",confidence:1})},transitions:m,fallbackText:"",maxRetries:0,onEnter:o.getOnEnterFn(),onExit:o.getOnExitFn(),isAction:!0,action:{execute:i,timeoutMs:o.getTimeoutMs(),onSuccess:u,onFailure:f,onTimeout:p}}}),h=[...a,...l],d=this.handoffBuilders.map(o=>{let i=o.getResumeNodeId(),u=o.getFallbackNodeId();if(i&&!e.has(i))throw new w(`Unknown node "${i}" referenced by resumeAt on handoff node "${o.getId()}". Known node IDs: ${n.join(", ")}.`);if(u&&!e.has(u))throw new w(`Unknown node "${u}" referenced by fallbackTo on handoff node "${o.getId()}". Known node IDs: ${n.join(", ")}.`);let f=[];return i&&f.push({targetNodeId:i,patterns:["resume"]}),u&&f.push({targetNodeId:u,patterns:["timeout"]}),{id:o.getId(),text:o.getText(),matcher:{patterns:["*"],normalize:p=>p,match:()=>({matched:!0,pattern:"*",confidence:1})},transitions:f,fallbackText:"",maxRetries:0,onEnter:o.getOnEnterFn(),onExit:o.getOnExitFn(),ignoreGlobals:o.getIgnoreGlobals(),isHandoff:!0,handoff:{reason:o.getReason(),timeoutMs:o.getTimeoutMs(),resumeNodeId:i,fallbackNodeId:u,queueMessage:o.getQueueMessage(),metadata:o.getMetadata()}}}),c=[...h,...d];return{flowId:this.flowId,version:this.version,nodes:c,globals:r.length>0?r:void 0}}};var I=class{data;constructor(e={}){this.data=e}set(e,n){this.data[e]=n}get(e){return this.data[e]}has(e){return Object.prototype.hasOwnProperty.call(this.data,e)}delete(e){delete this.data[e]}clear(){this.data={}}all(){return{...this.data}}};async function S(s,e=5e3){return new Promise((n,t)=>{let r=setTimeout(()=>{t(new Error("Handler execution timed out"))},e);Promise.resolve(s()).then(a=>{clearTimeout(r),n(a)}).catch(a=>{clearTimeout(r),t(a)})})}var C=class{constructor(e){this.flow=e}flow;createInitialState(e){return{waUserId:e,flowId:this.flow.flowId,flowVersion:this.flow.version,currentNodeId:this.flow.nodes[0]?.id||"",context:{},retryCount:0,status:"active"}}async executeInitialEnter(e){if(e.status!=="active")return e;let n=this.flow.nodes.find(t=>t.id===e.currentNodeId);if(n&&n.onEnter){let t=new I({...e.context});return await this.safelyExecuteHook(n.onEnter,t,{fromNodeId:void 0,toNodeId:n.id,isInitial:!0},`onEnter (initial) on node "${n.id}"`),{...e,context:t.all()}}return e}async executeActionNode(e){let n=this.flow.nodes.find(r=>r.id===e.currentNodeId);if(!n||!n.isAction)return{nextState:e,outboundText:null};let t=new I({...e.context});return await this.resolveDestination(e,n,n.id,"",t)}isGlobalIgnored(e,n){if(e.ignoreGlobals===!0)return!0;if(Array.isArray(e.ignoreGlobals)){let t=e.ignoreGlobals;if(t.includes(n.id)||n.patterns&&n.patterns.some(r=>t.includes(r)))return!0}return!1}async safelyExecuteHook(e,n,t,r){if(e)try{await S(()=>e(n,t))}catch(a){console.error(`[FSMEngine] ${r} hook failed or timed out:`,a)}}async resolveDestination(e,n,t,r,a,l,h=0){if(h>10)return console.error("[FSMEngine] Maximum action chaining depth exceeded (depth > 10). Breaking loop."),{nextState:{...e,context:a.all(),status:"escalated"},outboundText:"An unexpected error occurred. Connecting you with an agent."};let d=this.flow.nodes.find(o=>o.id===t);if(!d)return{nextState:{...e,context:a.all(),status:"completed"},outboundText:null,matchedGlobalId:l};if(await this.safelyExecuteHook(d.onEnter,a,{fromNodeId:n.id,toNodeId:d.id,triggerText:r,isInitial:!1},`onEnter on node "${d.id}"`),d.isAction&&d.action){let o=d.action,i=o.timeoutMs??5e3,u=!1,f=null,p=!1,m=null;try{f=await S(()=>o.execute(a),i),u=!0}catch(g){g?.message?.includes("timed out")&&(p=!0),m=g}if(u){if(o.onSuccess?.handler)try{await S(()=>o.onSuccess.handler(f,a),i)}catch(y){console.error(`[FSMEngine] onSuccess handler on action node "${d.id}" failed:`,y)}let g=o.onSuccess?.targetNodeId;return await this.safelyExecuteHook(d.onExit,a,{fromNodeId:d.id,toNodeId:g,triggerText:r},`onExit on action node "${d.id}"`),g?await this.resolveDestination(e,d,g,r,a,l,h+1):{nextState:{...e,context:a.all(),status:"completed"},outboundText:null,matchedGlobalId:l}}else if(p){let g=o.onTimeout??o.onFailure;if(g?.handler)try{await S(()=>g.handler(m,a),i)}catch(b){console.error(`[FSMEngine] onTimeout handler on action node "${d.id}" failed:`,b)}let y=g?.targetNodeId;return await this.safelyExecuteHook(d.onExit,a,{fromNodeId:d.id,toNodeId:y,triggerText:r},`onExit on action node "${d.id}"`),y?await this.resolveDestination(e,d,y,r,a,l,h+1):{nextState:{...e,context:a.all(),status:"completed"},outboundText:null,matchedGlobalId:l}}else{let g=o.onFailure;if(g?.handler)try{await S(()=>g.handler(m,a),i)}catch(b){console.error(`[FSMEngine] onFailure handler on action node "${d.id}" failed:`,b)}let y=g?.targetNodeId;return await this.safelyExecuteHook(d.onExit,a,{fromNodeId:d.id,toNodeId:y,triggerText:r},`onExit on action node "${d.id}"`),y?await this.resolveDestination(e,d,y,r,a,l,h+1):{nextState:{...e,context:a.all(),status:"completed"},outboundText:null,matchedGlobalId:l}}}if(d.isHandoff){let o=d.handoff,i={state:"pending",reason:o?.reason,requestedAt:Date.now(),resumeNodeId:o?.resumeNodeId,fallbackNodeId:o?.fallbackNodeId,timeoutMs:o?.timeoutMs??600*1e3,queueMessage:o?.queueMessage,metadata:o?.metadata?{...o.metadata}:void 0};return{nextState:{...e,currentNodeId:d.id,retryCount:0,context:a.all(),status:"handoff",handoff:i},outboundText:d.text,matchedGlobalId:l}}return{nextState:{...e,currentNodeId:d.id,retryCount:0,context:a.all(),status:"active"},outboundText:d.text,matchedGlobalId:l}}async executeGlobalTransition(e,n,t,r,a,l){if(t.responseFn)try{await S(()=>t.responseFn({raw:a,pattern:r},l))}catch(h){console.error(`[FSMEngine] Global response handler for "${t.id}" failed or timed out:`,h)}return await this.safelyExecuteHook(n.onExit,l,{fromNodeId:n.id,toNodeId:t.targetNodeId,triggerText:a},`onExit on node "${n.id}"`),await this.resolveDestination(e,n,t.targetNodeId,a,l,t.id)}async transition(e,n){if(e.status==="completed"||e.status==="escalated"||e.status==="abandoned")return{nextState:e,outboundText:null};if(e.status==="handoff"){let o=this.flow.nodes.find(p=>p.id===e.currentNodeId);if(!o)return{nextState:e,outboundText:null};let i=new I({...e.context}),f=(this.flow.globals??[]).filter(p=>!this.isGlobalIgnored(o,p));for(let p of f.filter(m=>m.priority==="high")){let m;try{m=p.matcher.match(n)}catch{m={matched:!1,pattern:"",confidence:0}}if(m.matched){let g=e.handoff?{...e.handoff,state:"resolved",resolvedAt:Date.now()}:void 0,y={...e,status:"active",handoff:g};return await this.executeGlobalTransition(y,o,p,m.pattern,n,i)}}for(let p of f.filter(m=>m.priority==="low")){let m;try{m=p.matcher.match(n)}catch{m={matched:!1,pattern:"",confidence:0}}if(m.matched){let g=e.handoff?{...e.handoff,state:"resolved",resolvedAt:Date.now()}:void 0,y={...e,status:"active",handoff:g};return await this.executeGlobalTransition(y,o,p,m.pattern,n,i)}}if(e.handoff?.state==="pending"){let p=e.handoff.queueMessage!==void 0?e.handoff.queueMessage:"You are in the queue. An agent will be with you shortly.";return{nextState:e,outboundText:p}}return{nextState:e,outboundText:null}}let t=this.flow.nodes.find(o=>o.id===e.currentNodeId);if(!t)return{nextState:e,outboundText:null};let r=new I({...e.context}),l=(this.flow.globals??[]).filter(o=>!this.isGlobalIgnored(t,o)),h=l.filter(o=>o.priority==="high"),d=l.filter(o=>o.priority==="low");for(let o of h){let i;try{i=o.matcher.match(n)}catch(u){console.error(`[FSMEngine] Global matcher "${o.id}" threw:`,u),i={matched:!1,pattern:"",confidence:0}}if(i.matched)return await this.executeGlobalTransition(e,t,o,i.pattern,n,r)}let c;try{c=t.matcher.match(n)}catch(o){console.error("Matcher threw an error:",o),c={matched:!1,pattern:"",confidence:0}}if(c.matched){let o=!0;if(t.responseFn)try{await S(()=>t.responseFn({raw:n,pattern:c.pattern},r))}catch(i){console.error("Response handler failed or timed out:",i),o=!1}if(o){let i=t.transitions[0];if(t.transitions.length>1)if(c.pattern!=="*"){let f=t.transitions.find(p=>p.patterns&&p.patterns.includes(c.pattern));if(f)i=f;else{let p=t.transitions.find(m=>m.patterns&&m.patterns.includes("*"));p&&(i=p)}}else{let f=t.transitions.find(p=>p.patterns&&p.patterns.includes("*"));f&&(i=f)}let u=i?.targetNodeId;return await this.safelyExecuteHook(t.onExit,r,{fromNodeId:t.id,toNodeId:u||void 0,triggerText:n},`onExit on node "${t.id}"`),u?await this.resolveDestination(e,t,u,n,r):{nextState:{...e,context:r.all(),status:"completed"},outboundText:null}}}for(let o of d){let i;try{i=o.matcher.match(n)}catch(u){console.error(`[FSMEngine] Global matcher "${o.id}" threw:`,u),i={matched:!1,pattern:"",confidence:0}}if(i.matched)return await this.executeGlobalTransition(e,t,o,i.pattern,n,r)}return this.handleFallback(e,t,r)}async handleFallback(e,n,t){let r=e.retryCount+1;if(r<=n.maxRetries){let a="Please try again.";if(typeof n.fallbackText=="function")try{let h=await S(()=>n.fallbackText(t));typeof h=="string"&&(a=h)}catch(h){console.error("Fallback generator function failed or timed out:",h)}else typeof n.fallbackText=="string"&&(a=n.fallbackText);return{nextState:{...e,retryCount:r,context:t.all()},outboundText:a}}else{let a="Let me connect you with a human \u2014 someone will be with you shortly.";if(n.onMaxRetriesFn)try{let h=await S(()=>n.onMaxRetriesFn(t));typeof h=="string"&&(a=h)}catch(h){console.error("onMaxRetries handler failed or timed out:",h)}return{nextState:{...e,retryCount:r,context:t.all(),status:"escalated"},outboundText:a}}}connectAgent(e,n,t){if(e.status!=="handoff"||!e.handoff)throw new Error(`Cannot connect agent: state status is "${e.status}", not "handoff".`);let r={...e.handoff,state:"connected",assignedAgentId:n,connectedAt:Date.now(),metadata:t?{...e.handoff.metadata,...t}:e.handoff.metadata};return{...e,handoff:r}}async resolveHandoff(e,n){if(e.status!=="handoff"||!e.handoff)throw new Error(`Cannot resolve handoff: state status is "${e.status}", not "handoff".`);let t=this.flow.nodes.find(d=>d.id===e.currentNodeId),r=new I({...e.context,...n?.contextUpdates||{}});n?.resolutionNotes&&r.set("handoff_resolution_notes",n.resolutionNotes);let a=n?.resumeNodeId??e.handoff.resumeNodeId;t&&await this.safelyExecuteHook(t.onExit,r,{fromNodeId:t.id,toNodeId:a,triggerText:"agent_resolve"},`onExit on handoff node "${t.id}"`);let l={...e.handoff,state:"resolved",resolvedAt:Date.now()},h={...e,status:"active",context:r.all(),handoff:l};return a?await this.resolveDestination(h,t??{id:e.currentNodeId},a,"agent_resolve",r):{nextState:{...h,status:"completed"},outboundText:null}}async checkHandoffTimeout(e,n=Date.now()){if(e.status!=="handoff"||!e.handoff||e.handoff.state!=="pending")return null;let t=e.handoff.timeoutMs??600*1e3;if(n-e.handoff.requestedAt<t)return null;let r=this.flow.nodes.find(c=>c.id===e.currentNodeId),a=new I({...e.context}),l={...e.handoff,state:"timed_out"},h=e.handoff.fallbackNodeId;r&&await this.safelyExecuteHook(r.onExit,a,{fromNodeId:r.id,toNodeId:h,triggerText:"handoff_timeout"},`onExit on handoff node "${r.id}"`);let d={...e,status:"active",context:a.all(),handoff:l};return h?await this.resolveDestination(d,r??{id:e.currentNodeId},h,"handoff_timeout",a):{nextState:{...d,status:"escalated"},outboundText:"Live agent request timed out. Please try again later or reach out via email."}}getAvailableCommands(e){let n=this.flow.nodes.find(r=>r.id===e.currentNodeId),t=[];if(e.status==="completed"||e.status==="escalated"||e.status==="abandoned")return{currentNodeId:e.currentNodeId,status:e.status,handoffState:e.handoff?.state,commands:[]};if(n&&this.flow.globals){for(let r of this.flow.globals)if(!this.isGlobalIgnored(n,r))for(let a of r.patterns||[r.id])t.push({kind:"global",command:a,targetNodeId:r.targetNodeId,description:`Global interrupt to "${r.targetNodeId}"`})}if(e.status==="handoff")return e.handoff?.state==="pending"?(t.push({kind:"handoff",command:"agent:connect",description:"Connect available human agent"}),t.push({kind:"handoff",command:"agent:timeout",targetNodeId:e.handoff?.fallbackNodeId,description:"Trigger handoff timeout fallback"})):e.handoff?.state==="connected"&&t.push({kind:"handoff",command:"agent:resolve",targetNodeId:e.handoff?.resumeNodeId,description:"Resolve live session and resume bot flow"}),{currentNodeId:e.currentNodeId,status:e.status,handoffState:e.handoff?.state,commands:t};if(n)if(n.isAction)t.push({kind:"action",command:"action:execute",targetNodeId:n.action?.onSuccess?.targetNodeId,description:"Execute async action handler"});else for(let r of n.transitions)for(let a of r.patterns||["*"])t.push({kind:"local",command:a,targetNodeId:r.targetNodeId,description:`Transition to "${r.targetNodeId}"`});return{currentNodeId:e.currentNodeId,status:e.status,handoffState:e.handoff?.state,commands:t}}getLegalTransitions(e){return this.getAvailableCommands(e)}};async function W(s,e,n){let t=new C(n.flow),r=await n.persistence.load(s);r||(r=t.createInitialState(s),r=await t.executeInitialEnter(r));let a=await t.transition(r,e);return await n.persistence.save(a.nextState),{nextState:a.nextState,result:a}}import fe,{Schema as k}from"mongoose";var E=class extends Error{constructor(n,t){super(`Optimistic concurrency violation for user ${n}. Expected version: ${t}`);this.waUserId=n;this.expectedVersion=t;this.name="OptimisticConcurrencyError"}waUserId;expectedVersion},X=new Map;function Z(s){let e=`${s.flowId}:${s.version}`;X.set(e,s)}var he=new k({waUserId:{type:String,required:!0,unique:!0,index:!0},flowId:{type:String,required:!0},flowVersion:{type:Number,required:!0},currentNodeId:{type:String,required:!0},context:{type:k.Types.Mixed,default:{}},retryCount:{type:Number,required:!0,default:0},status:{type:String,required:!0,enum:["active","escalated","completed","abandoned","handoff"],default:"active"},handoff:{type:k.Types.Mixed},version:{type:Number,required:!0,default:0}}),pe=new k({id:{type:String,required:!0},text:{type:String,required:!0},patterns:[{type:String}],fallbackText:{type:k.Types.Mixed},maxRetries:{type:Number,required:!0}}),ee=new k({flowId:{type:String,required:!0},version:{type:Number,required:!0},nodes:[pe]});ee.index({flowId:1,version:1},{unique:!0});var G=class{ConversationStateModel;FlowDefinitionModel;constructor(e=fe.connection){this.ConversationStateModel=e.models.ConversationState||e.model("ConversationState",he),this.FlowDefinitionModel=e.models.FlowDefinition||e.model("FlowDefinition",ee)}async load(e){let n=await this.ConversationStateModel.findOne({waUserId:e});return n?{waUserId:n.waUserId,flowId:n.flowId,flowVersion:n.flowVersion,currentNodeId:n.currentNodeId,context:n.context instanceof Map?Object.fromEntries(n.context):n.context,retryCount:n.retryCount,status:n.status,version:n.version}:null}async save(e){let n=e.version;if(n===void 0)try{await new this.ConversationStateModel({waUserId:e.waUserId,flowId:e.flowId,flowVersion:e.flowVersion,currentNodeId:e.currentNodeId,context:e.context,retryCount:e.retryCount,status:e.status,version:1}).save()}catch(t){throw t.code===11e3?new E(e.waUserId,0):t}else if((await this.ConversationStateModel.updateOne({waUserId:e.waUserId,version:n},{$set:{flowId:e.flowId,flowVersion:e.flowVersion,currentNodeId:e.currentNodeId,context:e.context,retryCount:e.retryCount,status:e.status,version:n+1}},{runValidators:!0})).matchedCount===0)throw new E(e.waUserId,n)}async loadFlowDefinition(e,n){let t=`${e}:${n}`,r=X.get(t);if(r)return r;throw new Error(`Flow "${e}" (v${n}) is not loaded/registered in the in-memory compiledRegistry. Active flows must be registered on application startup by calling registerFlowDefinition() or compiling a flow with bot.compile() to preserve execution callbacks and handlers.`)}async saveFlowDefinition(e){Z(e);let n=e.nodes.map(t=>({id:t.id,text:t.text,patterns:t.matcher.patterns,fallbackText:typeof t.fallbackText=="string"?t.fallbackText:"[Dynamic Handler]",maxRetries:t.maxRetries}));await this.FlowDefinitionModel.updateOne({flowId:e.flowId,version:e.version},{$set:{flowId:e.flowId,version:e.version,nodes:n}},{upsert:!0,runValidators:!0})}},U=class{constructor(e){this.options=e}options;async load(e){return this.options.load(e)}async save(e){return this.options.save(e)}async loadFlowDefinition(e,n){return this.options.loadFlowDefinition(e,n)}async saveFlowDefinition(e){return this.options.saveFlowDefinition(e)}};var z=class{constructor(e){this.redis=e}redis;async checkAndLock(e,n=21600){if(!e)throw new Error("wamid is required for idempotency check");let t=`dedup:${e}`;return await this.redis.set(t,"1","EX",n,"NX")==="OK"}async release(e){if(e){let n=`dedup:${e}`;await this.redis.del(n)}}};import{Flaresend as me}from"flaresend";var M=class extends Error{isAmbiguous;statusCode;code;constructor(e,n,t,r){super(e),this.name="FlaresendSendError",this.isAmbiguous=n,this.statusCode=t,this.code=r}},D=class extends M{constructor(e,n,t){super(e,!1,n,t),this.name="KnownNotSentError"}},B=class extends M{constructor(e,n,t){super(e,!0,n,t),this.name="AmbiguousSendError"}};function te(s){if(s instanceof M)return s;let e=s?.message||String(s),n=typeof s?.status=="number"?s.status:typeof s?.statusCode=="number"?s.statusCode:typeof s?.response?.status=="number"?s.response.status:void 0,t=s?.code||s?.errno;return n!==void 0&&n>=400&&n<500||t==="ENOTFOUND"||t==="ECONNREFUSED"||t==="EADDRNOTAVAIL"?new D(`Flaresend rejected request (Status: ${n??"N/A"}, Code: ${t??"N/A"}): ${e}`,n,t):new B(`Unconfirmed delivery status (Status: ${n??"N/A"}, Code: ${t??"N/A"}): ${e}`,n,t)}var K=class{sdk=null;isMock;constructor(e={}){this.isMock=!!e.mock;let n=e.apiKey||process.env.FLARESEND_API_KEY||"";if(!this.isMock){if(!n)throw new Error("FlaresendClient: FLARESEND_API_KEY is missing and mock mode is disabled. Provide a valid API key or explicitly set options.mock = true.");this.sdk=new me(n,{baseUrl:e.baseUrl})}}async sendMessage(e,n){if(this.isMock||!this.sdk)return console.warn(`[FlaresendClient MOCK] Sending message to ${e}: ${n}`),{success:!0,messageId:`mock-fs-${Date.now()}`};try{let t=await this.sdk.messages.send({recipients:[e],type:"text",text:n});return{success:t.success,messageId:t.successful_recipients?.[0]}}catch(t){let r=te(t);throw r.isAmbiguous?console.warn(`[AMBIGUOUS_SEND_FAILURE] Flaresend send to ${e} resulted in ambiguous error:`,r.message):console.error(`[KNOWN_NOT_SENT_FAILURE] Flaresend send to ${e} failed before transmission:`,r.message),r}}};import{createHmac as ge,timingSafeEqual as we}from"crypto";function ne(s){let e=[],n=[];if(!s)return{messages:e,statuses:n};if(Array.isArray(s.messages))for(let t of s.messages)t&&t.id&&t.from&&e.push({id:t.id,from:t.from,text:t.text?.body||t.text||"",timestamp:Number(t.timestamp)||Date.now()});if(Array.isArray(s.statuses))for(let t of s.statuses)t&&t.id&&n.push({id:t.id,status:t.status||"",recipient_id:t.recipient_id||t.recipient||"",timestamp:Number(t.timestamp)||Date.now()});if(Array.isArray(s.entry)){for(let t of s.entry)if(Array.isArray(t.changes))for(let r of t.changes){let a=r.value;if(a){if(Array.isArray(a.messages))for(let l of a.messages)l&&l.id&&l.from&&e.push({id:l.id,from:l.from,text:l.text?.body||l.text||"",timestamp:Number(l.timestamp)||Date.now()});if(Array.isArray(a.statuses))for(let l of a.statuses)l&&l.id&&n.push({id:l.id,status:l.status||"",recipient_id:l.recipient_id||l.recipient||"",timestamp:Number(l.timestamp)||Date.now()})}}}return{messages:e,statuses:n}}function F(s){if(!s.webhookSecret&&!s.allowUnsigned)throw new Error("Webhook security violation: webhookSecret is not configured, and allowUnsigned is not set to true. You must explicitly provide webhookSecret or set allowUnsigned: true to allow unverified payloads.")}function re(s,e,n){if(!e||!n)return!1;let t=e.startsWith("sha256=")?e.substring(7):e,r=ge("sha256",n);r.update(s);let a=r.digest("hex");try{return we(Buffer.from(t,"hex"),Buffer.from(a,"hex"))}catch{return!1}}async function T(s,e){F(e);let{idempotencyService:n,queue:t,webhookSecret:r}=e;if(r&&!e.allowUnsigned){let c=s.headers["x-hub-signature-256"]||s.headers["x-flaresend-signature"],o=s.rawBody;if(!o)throw new Error("Webhook signature verification failed on configuration: webhookSecret is active, but raw request body capture (e.g. req.rawBody) is missing. Please configure a raw-body capture middleware/plugin to preserve exact wire bytes.");if(!re(o,c,r))return{status:401,body:{error:"Unauthorized: Invalid webhook signature"}}}let{messages:a,statuses:l}=ne(s.body);if(a.length===0&&l.length===0)return{status:400,body:{error:"Bad Request: Invalid payload structure or missing entries"}};let h=0,d=0;for(let c of a)if(await n.checkAndLock(c.id))try{await t.add("whatsapp-message",{wamid:c.id,waUserId:c.from,messageText:c.text,timestamp:c.timestamp}),h++}catch(i){throw await n.release(c.id),i}else d++;return{status:200,body:{status:"success",processed:{enqueuedMessages:h,duplicateMessages:d,statusUpdates:l.length}}}}function j(s){return F(s),async(e,n)=>{let t=e.rawBody;if(Buffer.isBuffer(t)?t=t.toString("utf8"):typeof t!="string"&&(t=void 0),s.webhookSecret&&!s.allowUnsigned&&!t)throw new Error("Webhook signature verification failed on configuration: webhookSecret is active, but raw request body capture (e.g. request.rawBody) is missing. Please configure a raw-body capture plugin (such as fastify-raw-body) to preserve exact wire bytes.");let r={body:e.body,rawBody:t,headers:e.headers},a=await T(r,s);return n.code(a.status).send(a.body)}}function ye(s){return j(s)}function oe(s){return F(s),async(e,n)=>{try{let t=e.rawBody;Buffer.isBuffer(t)?t=t.toString("utf8"):typeof t!="string"&&(t=void 0);let r={body:e.body,rawBody:t,headers:e.headers},a=await T(r,s);n.status(a.status).json(a.body)}catch(t){n.status(500).json({error:t.message})}}}function xe(s){return oe(s)}function be(s){return new Promise((e,n)=>{let t="";s.on("data",r=>{t+=r}),s.on("end",()=>{e(t)}),s.on("error",r=>{n(r)})})}function se(s){return F(s),async(e,n)=>{try{let t=await be(e),r=null;if(t)try{r=JSON.parse(t)}catch{r=t}let a={body:r,rawBody:t,headers:e.headers},l=await T(a,s);n.writeHead(l.status,{"Content-Type":"application/json"}),n.end(JSON.stringify(l.body))}catch(t){n.writeHead(500,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:t.message}))}}}function ie(s){return se(s)}var ve=ie;import{Worker as de,Queue as Ie}from"bullmq";import{randomUUID as ae}from"crypto";var R=class extends Error{constructor(n,t){super(t||`Failed to acquire concurrency lease for user "${n}" within timeout.`);this.waUserId=n;this.name="ConcurrencyLockError"}waUserId},L=class{locks=new Map;async acquire(e,n){let t=n?.ttlMs??1e4,r=n?.acquireTimeoutMs??5e3,a=n?.retryIntervalMs??50,h=`${n?.keyPrefix??"fsm:lock:"}${e}`,d=ae(),c=Date.now();for(;;){let o=Date.now(),i=this.locks.get(h);if(!i||i.expiresAt<=o)return this.locks.set(h,{token:d,expiresAt:o+t}),{leaseKey:h,token:d,waUserId:e,acquiredAt:o,ttlMs:t,release:async()=>{let f=this.locks.get(h);return f&&f.token===d?(this.locks.delete(h),!0):!1}};if(Date.now()-c>=r)throw new R(e,`Failed to acquire in-memory concurrency lease for user "${e}" within ${r}ms.`);await new Promise(u=>setTimeout(u,a))}}async isLocked(e,n="fsm:lock:"){let t=`${n}${e}`,r=this.locks.get(t);return r?r.expiresAt>Date.now():!1}clear(){this.locks.clear()}},q=class s{constructor(e){this.redisClient=e}redisClient;static UNLOCK_SCRIPT=`
|
|
2
|
+
if redis.call("get", KEYS[1]) == ARGV[1] then
|
|
3
|
+
return redis.call("del", KEYS[1])
|
|
4
|
+
else
|
|
5
|
+
return 0
|
|
6
|
+
end
|
|
7
|
+
`;async acquire(e,n){let t=n?.ttlMs??1e4,r=n?.acquireTimeoutMs??5e3,a=n?.retryIntervalMs??50,h=`${n?.keyPrefix??"fsm:lock:"}${e}`,d=ae(),c=Date.now();for(;;){try{let i=await this.redisClient.set(h,d,"PX",t,"NX");if(i==="OK"||i===1){let u=Date.now();return{leaseKey:h,token:d,waUserId:e,acquiredAt:u,ttlMs:t,release:async()=>{try{return await this.redisClient.eval(s.UNLOCK_SCRIPT,1,h,d)===1}catch(f){return console.error(`[RedisConcurrencyLease] Failed to release lease for user ${e}:`,f),!1}}}}}catch(i){console.error(`[RedisConcurrencyLease] Redis error while acquiring lock for ${e}:`,i)}if(Date.now()-c>=r)throw new R(e,`Failed to acquire Redis concurrency lease for user "${e}" within ${r}ms.`);let o=Math.floor(Math.random()*20);await new Promise(i=>setTimeout(i,a+o))}}async isLocked(e,n="fsm:lock:"){let t=`${n}${e}`;return await this.redisClient.get(t)!==null}};function J(s){return s&&typeof s.set=="function"?new q(s):new L}async function ce(s,e){let{waUserId:n,messageText:t}=s;if(!e.flow.nodes||e.flow.nodes.length===0)throw new Error(`[FSM Worker] Flow definition "${e.flow.flowId}" is invalid: no nodes defined.`);let r=null;e.leaseService&&(r=await e.leaseService.acquire(n,e.leaseOptions));try{let{nextState:a,result:l}=await W(n,t,{persistence:e.persistence,flow:e.flow}),h=a,d=!1;if(l.outboundText)try{let c=await e.flaresendClient.sendMessage(n,l.outboundText);if(d=!0,c?.messageId)try{let o=await e.persistence.load(n);o&&(o.context.lastOutboundMessageId=c.messageId,await e.persistence.save(o),h=o)}catch(o){console.warn(`Failed to persist lastOutboundMessageId for ${n}`,o)}}catch(c){let o=c?.isAmbiguous??!0;if(o?console.warn(`[AMBIGUOUS_SEND_FAILURE] Outbound send failed with unconfirmed delivery status for user ${n}. State saved successfully. Delegating to resend queue with conservative initial backoff.`,c):console.error(`[KNOWN_NOT_SENT_FAILURE] Outbound send failed before transmission for user ${n}. State saved successfully. Delegating to resend queue with standard initial backoff.`,c),e.resendQueue){let i=o?5e3:1e3;await e.resendQueue.add("resend-message",{waUserId:n,outboundText:l.outboundText,isAmbiguous:o},{attempts:3,backoff:{type:"exponential",delay:i}})}else throw c}return{nextState:h,sentReply:d,result:l}}finally{if(r)try{await r.release()}catch(a){console.warn(`[FSM Worker] Failed to release concurrency lease for user ${n}:`,a)}}}function Se(s){let e=s.queueName||"webhook-messages",n=s.resendQueueName||`${e}-resends`,t=new Ie(n,{connection:s.connection}),r=new de(n,async d=>{let{waUserId:c,outboundText:o,isAmbiguous:i}=d.data;i?console.warn(`[Resend Worker AMBIGUOUS_RETRY] Attempting delivery retry for unconfirmed send (user: ${c}, job: ${d.id})`):console.log(`[Resend Worker STANDARD_RETRY] Attempting delivery retry for known-not-sent message (user: ${c}, job: ${d.id})`);let u=await s.flaresendClient.sendMessage(c,o);if(u?.messageId)try{let f=await s.persistence.load(c);f&&(f.context.lastOutboundMessageId=u.messageId,await s.persistence.save(f))}catch(f){console.warn(`[Resend Worker] Failed to update lastOutboundMessageId in state for user ${c}`,f)}},{connection:s.connection,concurrency:s.concurrency||1});r.on("completed",d=>{console.log(`[Resend Worker] Resend job ${d.id} completed successfully.`)}),r.on("failed",(d,c)=>{console.error(`[Resend Worker] Resend job ${d?.id} failed after maximum retry attempts. Error:`,c)});let a=s.leaseService??(s.enableConcurrencyLease!==!1&&s.connection?J(s.connection):void 0),l=new de(e,async d=>{console.log(`[FSM Worker] Received queued job ${d.id} for user: ${d.data.waUserId}`);let{wamid:c,waUserId:o,messageText:i,timestamp:u}=d.data;if(!o||typeof i!="string")throw new Error(`[FSM Worker] Invalid job payload: ${JSON.stringify(d.data)}`);return await ce({wamid:c,waUserId:o,messageText:i,timestamp:u},{persistence:s.persistence,flow:s.flow,flaresendClient:s.flaresendClient,resendQueue:t,leaseService:a,leaseOptions:s.leaseOptions})},{connection:s.connection,concurrency:s.concurrency||1});l.on("completed",d=>{console.log(`[FSM Worker] Job ${d.id} completed successfully.`)}),l.on("failed",(d,c)=>{console.error(`[FSM Worker] Job ${d?.id} failed with error:`,c)});let h=l.close.bind(l);return l.close=async()=>{console.log("[FSM Worker] Initiating graceful shutdown of FSM worker and companion resend system..."),await Promise.all([h(),r.close(),t.close()])},l}var Q=class{traces=[];listeners=[];constructor(e){e&&(this.traces=[...e])}logTurn(e){let n={...e,id:`turn_${Date.now()}_${Math.random().toString(36).substring(2,7)}`,timestamp:Date.now()};return this.traces.push(n),this.listeners.forEach(t=>t(n)),n}onTurn(e){return this.listeners.push(e),()=>{this.listeners=this.listeners.filter(n=>n!==e)}}getTraces(){return[...this.traces]}getSessions(){let e=new Map;for(let t of this.traces)e.has(t.sessionId)||e.set(t.sessionId,[]),e.get(t.sessionId).push(t);let n=[];return e.forEach((t,r)=>{t.sort((h,d)=>h.timestamp-d.timestamp);let a=t[t.length-1],l=t.reduce((h,d)=>h+(d.retryCount>0?1:0),0);n.push({sessionId:r,flowId:t[0].flowId,startTime:t[0].timestamp,endTime:a.timestamp,turns:t,finalStatus:a.status,totalRetries:l})}),n.sort((t,r)=>r.endTime-t.endTime)}computeMetrics(e){let n=this.getSessions(),t=n.length;if(t===0)return{totalSessions:0,completedSessions:0,escalatedSessions:0,activeSessions:0,containmentRate:100,completionRate:0,escalationRate:0,avgTurnsPerSession:0,totalRetries:0,nodeStats:{}};let r=0,a=0,l=0,h=0,d=0,c={};if(e)for(let p of e.nodes)c[p.id]={visits:0,retries:0,dropOffs:0,unhandledInputs:[],patternBreakdown:{}};for(let p of n){h+=p.turns.length,p.finalStatus==="completed"?r++:p.finalStatus==="escalated"?a++:l++;for(let m=0;m<p.turns.length;m++){let g=p.turns[m],y=m===p.turns.length-1;c[g.fromNodeId]||(c[g.fromNodeId]={visits:0,retries:0,dropOffs:0,unhandledInputs:[],patternBreakdown:{}});let b=c[g.fromNodeId];b.visits++,g.retryCount>0&&(b.retries++,b.unhandledInputs.includes(g.rawInput)||b.unhandledInputs.push(g.rawInput));let x=g.matchedPattern||"[unmatched]";b.patternBreakdown[x]=(b.patternBreakdown[x]||0)+1,y&&p.finalStatus!=="completed"&&b.dropOffs++}}let o=t>0?Math.round((t-a)/t*100):100,i=Math.round(r/t*100),u=Math.round(a/t*100),f=Number((h/t).toFixed(1));return{totalSessions:t,completedSessions:r,escalatedSessions:a,activeSessions:l,containmentRate:o,completionRate:i,escalationRate:u,avgTurnsPerSession:f,totalRetries:d,nodeStats:c}}clear(){this.traces=[]}};var V=class{constructor(e){this.flow=e}flow;async runScenario(e){let n=Date.now(),t=new C(this.flow),r=t.createInitialState(`eval_user_${e.id}`);e.initialContext&&(r={...r,context:{...e.initialContext}});let a=[],l=new Set([r.currentNodeId]),h=!0;for(let d=0;d<e.steps.length;d++){let c=e.steps[d],o=await t.transition(r,c.input);r=o.nextState,l.add(r.currentNodeId);let i=!0,u=[];if(c.expectedNodeId&&r.currentNodeId!==c.expectedNodeId&&(i=!1,u.push(`Expected node "${c.expectedNodeId}", but transitioned to "${r.currentNodeId}"`)),c.expectedOutboundPattern){let f=o.outboundText??"";typeof c.expectedOutboundPattern=="string"?f.toLowerCase().includes(c.expectedOutboundPattern.toLowerCase())||(i=!1,u.push(`Outbound text "${f}" did not contain expected substring "${c.expectedOutboundPattern}"`)):c.expectedOutboundPattern instanceof RegExp&&(c.expectedOutboundPattern.test(f)||(i=!1,u.push(`Outbound text "${f}" did not match regex pattern ${c.expectedOutboundPattern}`)))}if(c.expectedContext)for(let[f,p]of Object.entries(c.expectedContext))r.context[f]!==p&&(i=!1,u.push(`Context key "${f}" expected value ${JSON.stringify(p)}, got ${JSON.stringify(r.context[f])}`));c.expectEscalated&&r.status!=="escalated"&&(i=!1,u.push(`Expected conversation to be escalated, but status was "${r.status}"`)),c.expectCompleted&&r.status!=="completed"&&(i=!1,u.push(`Expected conversation to be completed, but status was "${r.status}"`)),i||(h=!1),a.push({stepIndex:d+1,input:c.input,actualNodeId:r.currentNodeId,expectedNodeId:c.expectedNodeId,actualOutbound:o.outboundText,expectedOutboundPattern:c.expectedOutboundPattern,actualStatus:r.status,actualContext:{...r.context},passed:i,failureReason:u.join("; ")})}return{scenarioId:e.id,scenarioName:e.name,passed:h,stepResults:a,durationMs:Date.now()-n,visitedNodeIds:Array.from(l)}}async runSuite(e){let n=Date.now(),t=[],r=new Set;for(let f of e){let p=await this.runScenario(f);t.push(p),p.visitedNodeIds.forEach(m=>r.add(m))}let a=e.length,l=t.filter(f=>f.passed).length,h=a-l,d=a>0?Math.round(l/a*100):0,c=this.flow.nodes.map(f=>f.id),o=c.filter(f=>r.has(f)),i=c.filter(f=>!r.has(f)),u=c.length>0?Math.round(o.length/c.length*100):100;return{totalScenarios:a,passedScenarios:l,failedScenarios:h,passRate:d,nodeCoveragePercentage:u,coveredNodeIds:o,uncoveredNodeIds:i,scenarioResults:t,totalDurationMs:Date.now()-n}}};export{O as ActionNodeBuilder,B as AmbiguousSendError,H as Bot,U as CallbackPersistenceAdapter,w as CompileError,R as ConcurrencyLockError,I as Context,V as Evaluator,C as FSMEngine,K as FlaresendClient,M as FlaresendSendError,P as FlowNodeBuilder,$ as GlobalTransitionBuilder,A as HandoffNodeBuilder,z as IdempotencyService,L as InMemoryConcurrencyLeaseService,D as KnownNotSentError,G as MongoosePersistenceAdapter,E as OptimisticConcurrencyError,q as RedisConcurrencyLeaseService,Q as TraceCollector,te as classifyFlaresendError,J as createConcurrencyLeaseService,oe as createExpressAdapter,Se as createFSMWorker,j as createFastifyAdapter,Y as createLiteralMatcher,se as createRawHttpAdapter,xe as expressAdapter,ne as extractWebhookPayload,ye as fastifyAdapter,T as handleWebhookCore,ve as httpAdapter,_ as matchesAny,ce as processFSMJob,ie as rawHttpAdapter,Z as registerFlowDefinition,W as runConversationStep,re as verifyWebhookSignature};
|
package/package.json
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@henry-dev/botkit",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
1
|
+
{
|
|
2
|
+
"name": "@henry-dev/botkit",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Deterministic, type-safe conversational state machine framework for chatbots.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"require": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup index.ts --format cjs,esm --dts --clean --minify --out-dir dist",
|
|
22
|
+
"dev": "tsup index.ts --format cjs,esm --watch --dts",
|
|
23
|
+
"lint": "tsc --noEmit",
|
|
24
|
+
"test": "vitest run --config ./vitest.config.ts"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"bullmq": "^5.80.8",
|
|
28
|
+
"flaresend": "^1.0.2",
|
|
29
|
+
"ioredis": "^5.11.1",
|
|
30
|
+
"mongoose": "^9.7.4"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^22.14.0",
|
|
34
|
+
"tsup": "^8.5.1",
|
|
35
|
+
"typescript": "~5.8.2",
|
|
36
|
+
"vitest": "^4.1.10"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"flaresend",
|
|
40
|
+
"whatsapp",
|
|
41
|
+
"techtafari"
|
|
42
|
+
],
|
|
43
|
+
"author": {
|
|
44
|
+
"name": "Tech Tafari",
|
|
45
|
+
"url": "https://github.com/mpiers110"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18.0.0"
|
|
49
|
+
}
|
|
6
50
|
}
|