@henry-dev/botkit 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2695 -0
- package/dist/index.d.cts +749 -0
- package/dist/index.d.ts +749 -0
- package/dist/index.js +2621 -0
- package/package.json +34 -6
- package/README.md +0 -3
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
import Redis from 'ioredis';
|
|
3
|
+
import { Worker, Queue } from 'bullmq';
|
|
4
|
+
|
|
5
|
+
interface MatchResult {
|
|
6
|
+
readonly matched: boolean;
|
|
7
|
+
readonly pattern: string;
|
|
8
|
+
readonly confidence: number;
|
|
9
|
+
}
|
|
10
|
+
interface MatcherOptions {
|
|
11
|
+
readonly boundary?: boolean;
|
|
12
|
+
readonly normalize?: boolean;
|
|
13
|
+
readonly excludeIf?: ReadonlyArray<string>;
|
|
14
|
+
}
|
|
15
|
+
interface CompiledMatcher {
|
|
16
|
+
readonly patterns: ReadonlyArray<string>;
|
|
17
|
+
readonly normalize: (text: string) => string;
|
|
18
|
+
readonly match: (text: string) => MatchResult;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
declare class Context {
|
|
22
|
+
private data;
|
|
23
|
+
constructor(initialData?: Record<string, any>);
|
|
24
|
+
set(key: string, value: any): void;
|
|
25
|
+
get(key: string): any;
|
|
26
|
+
has(key: string): boolean;
|
|
27
|
+
delete(key: string): void;
|
|
28
|
+
clear(): void;
|
|
29
|
+
all(): Record<string, any>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface Transition {
|
|
33
|
+
readonly targetNodeId: string;
|
|
34
|
+
readonly patterns?: ReadonlyArray<string>;
|
|
35
|
+
}
|
|
36
|
+
interface LifecycleEventInfo {
|
|
37
|
+
readonly fromNodeId?: string;
|
|
38
|
+
readonly toNodeId?: string;
|
|
39
|
+
readonly triggerText?: string;
|
|
40
|
+
readonly isInitial?: boolean;
|
|
41
|
+
}
|
|
42
|
+
type LifecycleHook = (ctx: Context, info: LifecycleEventInfo) => void | Promise<void>;
|
|
43
|
+
interface ActionBranch<T = any> {
|
|
44
|
+
readonly targetNodeId: string;
|
|
45
|
+
readonly handler?: (data: T, ctx: Context) => void | Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
type ActionFunction<T = any> = (ctx: Context) => Promise<T> | T;
|
|
48
|
+
interface ActionDefinition<T = any> {
|
|
49
|
+
readonly execute: ActionFunction<T>;
|
|
50
|
+
readonly timeoutMs?: number;
|
|
51
|
+
readonly onSuccess?: ActionBranch<T>;
|
|
52
|
+
readonly onFailure?: ActionBranch<any>;
|
|
53
|
+
readonly onTimeout?: ActionBranch<Error>;
|
|
54
|
+
}
|
|
55
|
+
interface GlobalTransition {
|
|
56
|
+
readonly id: string;
|
|
57
|
+
readonly matcher: CompiledMatcher;
|
|
58
|
+
readonly patterns?: ReadonlyArray<string>;
|
|
59
|
+
readonly targetNodeId: string;
|
|
60
|
+
readonly responseFn?: (answer: {
|
|
61
|
+
raw: string;
|
|
62
|
+
pattern: string;
|
|
63
|
+
}, ctx: Context) => void | Promise<void>;
|
|
64
|
+
readonly priority: "high" | "low";
|
|
65
|
+
}
|
|
66
|
+
interface HandoffSession {
|
|
67
|
+
readonly state: "pending" | "connected" | "resolved" | "timed_out";
|
|
68
|
+
readonly reason?: string;
|
|
69
|
+
readonly assignedAgentId?: string;
|
|
70
|
+
readonly requestedAt: number;
|
|
71
|
+
readonly connectedAt?: number;
|
|
72
|
+
readonly resolvedAt?: number;
|
|
73
|
+
readonly resumeNodeId?: string;
|
|
74
|
+
readonly timeoutMs?: number;
|
|
75
|
+
readonly fallbackNodeId?: string;
|
|
76
|
+
readonly queueMessage?: string | null;
|
|
77
|
+
readonly metadata?: Record<string, any>;
|
|
78
|
+
}
|
|
79
|
+
interface HandoffDefinition {
|
|
80
|
+
readonly reason?: string;
|
|
81
|
+
readonly timeoutMs?: number;
|
|
82
|
+
readonly resumeNodeId?: string;
|
|
83
|
+
readonly fallbackNodeId?: string;
|
|
84
|
+
readonly queueMessage?: string | null;
|
|
85
|
+
readonly metadata?: Record<string, any>;
|
|
86
|
+
}
|
|
87
|
+
interface FlowNode {
|
|
88
|
+
readonly id: string;
|
|
89
|
+
readonly text: string;
|
|
90
|
+
readonly matcher: CompiledMatcher;
|
|
91
|
+
readonly transitions: ReadonlyArray<Transition>;
|
|
92
|
+
readonly fallbackText: string | ((ctx: Context) => string | Promise<string>);
|
|
93
|
+
readonly maxRetries: number;
|
|
94
|
+
readonly onMaxRetriesFn?: (ctx: Context) => void | Promise<void>;
|
|
95
|
+
readonly responseFn?: (answer: {
|
|
96
|
+
raw: string;
|
|
97
|
+
pattern: string;
|
|
98
|
+
}, ctx: Context) => void | Promise<void>;
|
|
99
|
+
readonly onEnter?: LifecycleHook;
|
|
100
|
+
readonly onExit?: LifecycleHook;
|
|
101
|
+
readonly ignoreGlobals?: boolean | ReadonlyArray<string>;
|
|
102
|
+
readonly isAction?: boolean;
|
|
103
|
+
readonly action?: ActionDefinition;
|
|
104
|
+
readonly isHandoff?: boolean;
|
|
105
|
+
readonly handoff?: HandoffDefinition;
|
|
106
|
+
}
|
|
107
|
+
interface FlowDefinition {
|
|
108
|
+
readonly flowId: string;
|
|
109
|
+
readonly version: number;
|
|
110
|
+
readonly nodes: ReadonlyArray<FlowNode>;
|
|
111
|
+
readonly globals?: ReadonlyArray<GlobalTransition>;
|
|
112
|
+
}
|
|
113
|
+
interface ConversationState {
|
|
114
|
+
readonly waUserId: string;
|
|
115
|
+
readonly flowId: string;
|
|
116
|
+
readonly flowVersion: number;
|
|
117
|
+
readonly currentNodeId: string;
|
|
118
|
+
readonly context: Record<string, any>;
|
|
119
|
+
readonly retryCount: number;
|
|
120
|
+
readonly status: "active" | "escalated" | "completed" | "abandoned" | "handoff";
|
|
121
|
+
readonly version?: number;
|
|
122
|
+
readonly handoff?: HandoffSession;
|
|
123
|
+
}
|
|
124
|
+
interface TransitionResult {
|
|
125
|
+
readonly nextState: ConversationState;
|
|
126
|
+
readonly outboundText: string | null;
|
|
127
|
+
readonly matchedGlobalId?: string;
|
|
128
|
+
readonly suspended?: boolean;
|
|
129
|
+
}
|
|
130
|
+
interface LegalCommandInfo {
|
|
131
|
+
readonly kind: "global" | "local" | "handoff" | "action";
|
|
132
|
+
readonly command: string;
|
|
133
|
+
readonly targetNodeId?: string;
|
|
134
|
+
readonly description?: string;
|
|
135
|
+
}
|
|
136
|
+
interface AvailableCommandsResult {
|
|
137
|
+
readonly currentNodeId: string;
|
|
138
|
+
readonly status: "active" | "escalated" | "completed" | "abandoned" | "handoff";
|
|
139
|
+
readonly handoffState?: "pending" | "connected" | "resolved" | "timed_out";
|
|
140
|
+
readonly commands: ReadonlyArray<LegalCommandInfo>;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
declare class CompileError extends Error {
|
|
144
|
+
constructor(message: string);
|
|
145
|
+
}
|
|
146
|
+
interface ActionDefinitionOptions<T = any> {
|
|
147
|
+
execute?: ActionFunction<T>;
|
|
148
|
+
timeoutMs?: number;
|
|
149
|
+
onSuccess?: string | ActionBranch<T>;
|
|
150
|
+
onFailure?: string | ActionBranch<any>;
|
|
151
|
+
onTimeout?: string | ActionBranch<Error>;
|
|
152
|
+
}
|
|
153
|
+
declare class ActionNodeBuilder {
|
|
154
|
+
private bot;
|
|
155
|
+
id: string;
|
|
156
|
+
private _actionFn?;
|
|
157
|
+
private _timeoutMs;
|
|
158
|
+
private _onSuccess?;
|
|
159
|
+
private _onFailure?;
|
|
160
|
+
private _onTimeout?;
|
|
161
|
+
private _onEnter?;
|
|
162
|
+
private _onExit?;
|
|
163
|
+
private _text;
|
|
164
|
+
constructor(bot: Bot, id: string, options?: ActionDefinitionOptions);
|
|
165
|
+
execute<T = any>(fn: ActionFunction<T>): this;
|
|
166
|
+
timeout(ms: number): this;
|
|
167
|
+
text(msg: string): this;
|
|
168
|
+
onSuccess<T = any>(targetNodeId: string, handler?: (data: T, ctx: Context) => void | Promise<void>): this;
|
|
169
|
+
onFailure(targetNodeId: string, handler?: (err: any, ctx: Context) => void | Promise<void>): this;
|
|
170
|
+
onTimeout(targetNodeId: string, handler?: (err: Error, ctx: Context) => void | Promise<void>): this;
|
|
171
|
+
onEnter(fn: LifecycleHook): this;
|
|
172
|
+
onExit(fn: LifecycleHook): this;
|
|
173
|
+
ask(text: string): FlowNodeBuilder;
|
|
174
|
+
action(id: string, options?: ActionDefinitionOptions): ActionNodeBuilder;
|
|
175
|
+
handoff(id: string, options?: HandoffNodeOptions): HandoffNodeBuilder;
|
|
176
|
+
global(matcher: CompiledMatcher | string[] | string, options?: GlobalTransitionOptions | string): GlobalTransitionBuilder;
|
|
177
|
+
getId(): string;
|
|
178
|
+
getText(): string;
|
|
179
|
+
getActionFn(): ActionFunction;
|
|
180
|
+
getTimeoutMs(): number;
|
|
181
|
+
getOnSuccess(): ActionBranch<any>;
|
|
182
|
+
getOnFailure(): ActionBranch<any>;
|
|
183
|
+
getOnTimeout(): ActionBranch<any>;
|
|
184
|
+
getOnEnterFn(): LifecycleHook;
|
|
185
|
+
getOnExitFn(): LifecycleHook;
|
|
186
|
+
}
|
|
187
|
+
interface HandoffNodeOptions {
|
|
188
|
+
reason?: string;
|
|
189
|
+
timeoutMs?: number;
|
|
190
|
+
resumeNodeId?: string;
|
|
191
|
+
fallbackNodeId?: string;
|
|
192
|
+
message?: string;
|
|
193
|
+
queueMessage?: string | null;
|
|
194
|
+
metadata?: Record<string, any>;
|
|
195
|
+
}
|
|
196
|
+
declare class HandoffNodeBuilder {
|
|
197
|
+
private bot;
|
|
198
|
+
id: string;
|
|
199
|
+
private _reason?;
|
|
200
|
+
private _timeoutMs;
|
|
201
|
+
private _resumeNodeId?;
|
|
202
|
+
private _fallbackNodeId?;
|
|
203
|
+
private _text;
|
|
204
|
+
private _queueMessage?;
|
|
205
|
+
private _metadata?;
|
|
206
|
+
private _onEnter?;
|
|
207
|
+
private _onExit?;
|
|
208
|
+
private _ignoreGlobals?;
|
|
209
|
+
constructor(bot: Bot, id: string, options?: HandoffNodeOptions);
|
|
210
|
+
reason(text: string): this;
|
|
211
|
+
timeout(ms: number): this;
|
|
212
|
+
resumeAt(targetNodeId: string): this;
|
|
213
|
+
fallbackTo(targetNodeId: string): this;
|
|
214
|
+
message(text: string): this;
|
|
215
|
+
queueMessage(text: string | null): this;
|
|
216
|
+
metadata(data: Record<string, any>): this;
|
|
217
|
+
onEnter(fn: LifecycleHook): this;
|
|
218
|
+
onExit(fn: LifecycleHook): this;
|
|
219
|
+
ignoreGlobals(ignore?: boolean | string[]): this;
|
|
220
|
+
ask(text: string): FlowNodeBuilder;
|
|
221
|
+
action(id: string, options?: ActionDefinitionOptions): ActionNodeBuilder;
|
|
222
|
+
handoff(id: string, options?: HandoffNodeOptions): HandoffNodeBuilder;
|
|
223
|
+
global(matcher: CompiledMatcher | string[] | string, options?: GlobalTransitionOptions | string): GlobalTransitionBuilder;
|
|
224
|
+
getId(): string;
|
|
225
|
+
getText(): string;
|
|
226
|
+
getReason(): string;
|
|
227
|
+
getTimeoutMs(): number;
|
|
228
|
+
getResumeNodeId(): string;
|
|
229
|
+
getFallbackNodeId(): string;
|
|
230
|
+
getQueueMessage(): string;
|
|
231
|
+
getMetadata(): Record<string, any>;
|
|
232
|
+
getOnEnterFn(): LifecycleHook;
|
|
233
|
+
getOnExitFn(): LifecycleHook;
|
|
234
|
+
getIgnoreGlobals(): boolean | string[];
|
|
235
|
+
}
|
|
236
|
+
interface GlobalTransitionOptions {
|
|
237
|
+
id?: string;
|
|
238
|
+
target?: string;
|
|
239
|
+
targetNodeId?: string;
|
|
240
|
+
priority?: "high" | "low";
|
|
241
|
+
response?: (answer: {
|
|
242
|
+
raw: string;
|
|
243
|
+
pattern: string;
|
|
244
|
+
}, ctx: Context) => void | Promise<void>;
|
|
245
|
+
matcherOptions?: MatcherOptions;
|
|
246
|
+
}
|
|
247
|
+
declare class GlobalTransitionBuilder {
|
|
248
|
+
private bot;
|
|
249
|
+
private _id?;
|
|
250
|
+
private _targetNodeId?;
|
|
251
|
+
private _patterns;
|
|
252
|
+
private _opts?;
|
|
253
|
+
private _matcher?;
|
|
254
|
+
private _priority;
|
|
255
|
+
private _responseFn?;
|
|
256
|
+
constructor(bot: Bot, matcher: CompiledMatcher | string[] | string, options?: GlobalTransitionOptions | string);
|
|
257
|
+
id(customId: string): this;
|
|
258
|
+
then(targetNodeId: string): this;
|
|
259
|
+
response(fn: (answer: {
|
|
260
|
+
raw: string;
|
|
261
|
+
pattern: string;
|
|
262
|
+
}, ctx: Context) => void | Promise<void>): this;
|
|
263
|
+
priority(p: "high" | "low"): this;
|
|
264
|
+
getId(defaultId: string): string;
|
|
265
|
+
getTargetNodeId(): string | undefined;
|
|
266
|
+
getPatterns(): string[];
|
|
267
|
+
getOpts(): MatcherOptions | undefined;
|
|
268
|
+
getMatcher(): CompiledMatcher | undefined;
|
|
269
|
+
getPriority(): "high" | "low";
|
|
270
|
+
getResponseFn(): (answer: {
|
|
271
|
+
raw: string;
|
|
272
|
+
pattern: string;
|
|
273
|
+
}, ctx: Context) => void | Promise<void>;
|
|
274
|
+
ask(text: string): FlowNodeBuilder;
|
|
275
|
+
action(id: string, options?: ActionDefinitionOptions): ActionNodeBuilder;
|
|
276
|
+
handoff(id: string, options?: HandoffNodeOptions): HandoffNodeBuilder;
|
|
277
|
+
global(matcher: CompiledMatcher | string[] | string, options?: GlobalTransitionOptions | string): GlobalTransitionBuilder;
|
|
278
|
+
}
|
|
279
|
+
interface PendingBranch {
|
|
280
|
+
patterns: string[];
|
|
281
|
+
opts?: MatcherOptions;
|
|
282
|
+
matcher?: CompiledMatcher;
|
|
283
|
+
targetNodeId?: string;
|
|
284
|
+
}
|
|
285
|
+
declare class FlowNodeBuilder {
|
|
286
|
+
private bot;
|
|
287
|
+
defaultId: string;
|
|
288
|
+
text: string;
|
|
289
|
+
private _customId?;
|
|
290
|
+
private _branches;
|
|
291
|
+
private _responseFn?;
|
|
292
|
+
private _fallbackText;
|
|
293
|
+
private _maxRetries;
|
|
294
|
+
private _onMaxRetriesFn?;
|
|
295
|
+
private _onEnter?;
|
|
296
|
+
private _onExit?;
|
|
297
|
+
private _ignoreGlobals?;
|
|
298
|
+
constructor(bot: Bot, defaultId: string, text: string);
|
|
299
|
+
getId(): string;
|
|
300
|
+
id(customId: string): this;
|
|
301
|
+
expect(matcher: CompiledMatcher | string[] | string, opts?: MatcherOptions): this;
|
|
302
|
+
then(targetNodeId: string): this;
|
|
303
|
+
response(fn: (answer: {
|
|
304
|
+
raw: string;
|
|
305
|
+
pattern: string;
|
|
306
|
+
}, ctx: Context) => void | Promise<void>): this;
|
|
307
|
+
fallback(textOrFn: string | ((ctx: Context) => string | Promise<string>)): this;
|
|
308
|
+
retries(n: number): this;
|
|
309
|
+
onMaxRetries(fn: (ctx: Context) => void | Promise<void>): this;
|
|
310
|
+
onEnter(fn: LifecycleHook): this;
|
|
311
|
+
onExit(fn: LifecycleHook): this;
|
|
312
|
+
ignoreGlobals(ignore?: boolean | string[]): this;
|
|
313
|
+
ask(text: string): FlowNodeBuilder;
|
|
314
|
+
action(id: string, options?: ActionDefinitionOptions): ActionNodeBuilder;
|
|
315
|
+
handoff(id: string, options?: HandoffNodeOptions): HandoffNodeBuilder;
|
|
316
|
+
global(matcher: CompiledMatcher | string[] | string, options?: GlobalTransitionOptions | string): GlobalTransitionBuilder;
|
|
317
|
+
getBranches(): ReadonlyArray<PendingBranch>;
|
|
318
|
+
getResponseFn(): (answer: {
|
|
319
|
+
raw: string;
|
|
320
|
+
pattern: string;
|
|
321
|
+
}, ctx: Context) => void | Promise<void>;
|
|
322
|
+
getFallbackText(): string | ((ctx: Context) => string | Promise<string>);
|
|
323
|
+
getMaxRetries(): number;
|
|
324
|
+
getOnMaxRetriesFn(): (ctx: Context) => void | Promise<void>;
|
|
325
|
+
getOnEnterFn(): LifecycleHook;
|
|
326
|
+
getOnExitFn(): LifecycleHook;
|
|
327
|
+
getIgnoreGlobals(): boolean | string[];
|
|
328
|
+
}
|
|
329
|
+
declare class Bot {
|
|
330
|
+
flowId: string;
|
|
331
|
+
version: number;
|
|
332
|
+
private builders;
|
|
333
|
+
private actionBuilders;
|
|
334
|
+
private handoffBuilders;
|
|
335
|
+
private globalBuilders;
|
|
336
|
+
constructor(flowId: string, version?: number);
|
|
337
|
+
ask(text: string): FlowNodeBuilder;
|
|
338
|
+
action(id: string, options?: ActionDefinitionOptions): ActionNodeBuilder;
|
|
339
|
+
handoff(id: string, options?: HandoffNodeOptions): HandoffNodeBuilder;
|
|
340
|
+
global(matcher: CompiledMatcher | string[] | string, options?: GlobalTransitionOptions | string): GlobalTransitionBuilder;
|
|
341
|
+
escalate(ctx: Context): void;
|
|
342
|
+
compile(): FlowDefinition;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
declare class OptimisticConcurrencyError extends Error {
|
|
346
|
+
waUserId: string;
|
|
347
|
+
expectedVersion: number;
|
|
348
|
+
constructor(waUserId: string, expectedVersion: number);
|
|
349
|
+
}
|
|
350
|
+
interface PersistenceAdapter {
|
|
351
|
+
load(waUserId: string): Promise<ConversationState | null>;
|
|
352
|
+
save(state: ConversationState): Promise<void>;
|
|
353
|
+
loadFlowDefinition(flowId: string, version: number): Promise<FlowDefinition | null>;
|
|
354
|
+
saveFlowDefinition(flow: FlowDefinition): Promise<void>;
|
|
355
|
+
}
|
|
356
|
+
declare function registerFlowDefinition(flow: FlowDefinition): void;
|
|
357
|
+
declare class MongoosePersistenceAdapter implements PersistenceAdapter {
|
|
358
|
+
private ConversationStateModel;
|
|
359
|
+
private FlowDefinitionModel;
|
|
360
|
+
constructor(connection?: mongoose.Connection);
|
|
361
|
+
load(waUserId: string): Promise<ConversationState | null>;
|
|
362
|
+
save(state: ConversationState): Promise<void>;
|
|
363
|
+
loadFlowDefinition(flowId: string, version: number): Promise<FlowDefinition | null>;
|
|
364
|
+
saveFlowDefinition(flow: FlowDefinition): Promise<void>;
|
|
365
|
+
}
|
|
366
|
+
interface CallbackPersistenceAdapterOptions {
|
|
367
|
+
load: (waUserId: string) => Promise<ConversationState | null>;
|
|
368
|
+
save: (state: ConversationState) => Promise<void>;
|
|
369
|
+
loadFlowDefinition: (flowId: string, version: number) => Promise<FlowDefinition | null>;
|
|
370
|
+
saveFlowDefinition: (flow: FlowDefinition) => Promise<void>;
|
|
371
|
+
}
|
|
372
|
+
declare class CallbackPersistenceAdapter implements PersistenceAdapter {
|
|
373
|
+
private options;
|
|
374
|
+
constructor(options: CallbackPersistenceAdapterOptions);
|
|
375
|
+
load(waUserId: string): Promise<ConversationState>;
|
|
376
|
+
save(state: ConversationState): Promise<void>;
|
|
377
|
+
loadFlowDefinition(flowId: string, version: number): Promise<FlowDefinition>;
|
|
378
|
+
saveFlowDefinition(flow: FlowDefinition): Promise<void>;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
declare class FSMEngine {
|
|
382
|
+
private flow;
|
|
383
|
+
constructor(flow: FlowDefinition);
|
|
384
|
+
createInitialState(waUserId: string): ConversationState;
|
|
385
|
+
executeInitialEnter(state: ConversationState): Promise<ConversationState>;
|
|
386
|
+
executeActionNode(state: ConversationState): Promise<TransitionResult>;
|
|
387
|
+
private isGlobalIgnored;
|
|
388
|
+
private safelyExecuteHook;
|
|
389
|
+
private resolveDestination;
|
|
390
|
+
private executeGlobalTransition;
|
|
391
|
+
transition(state: ConversationState, inboundText: string): Promise<TransitionResult>;
|
|
392
|
+
private handleFallback;
|
|
393
|
+
connectAgent(state: ConversationState, agentId: string, metadata?: Record<string, any>): ConversationState;
|
|
394
|
+
resolveHandoff(state: ConversationState, options?: {
|
|
395
|
+
resumeNodeId?: string;
|
|
396
|
+
resolutionNotes?: string;
|
|
397
|
+
contextUpdates?: Record<string, any>;
|
|
398
|
+
}): Promise<TransitionResult>;
|
|
399
|
+
checkHandoffTimeout(state: ConversationState, currentTime?: number): Promise<TransitionResult | null>;
|
|
400
|
+
getAvailableCommands(state: ConversationState): AvailableCommandsResult;
|
|
401
|
+
getLegalTransitions(state: ConversationState): AvailableCommandsResult;
|
|
402
|
+
}
|
|
403
|
+
declare function runConversationStep(waUserId: string, messageText: string, options: {
|
|
404
|
+
persistence: PersistenceAdapter;
|
|
405
|
+
flow: FlowDefinition;
|
|
406
|
+
}): Promise<{
|
|
407
|
+
nextState: ConversationState;
|
|
408
|
+
result: TransitionResult;
|
|
409
|
+
}>;
|
|
410
|
+
|
|
411
|
+
declare function createLiteralMatcher(patterns: string[], opts?: MatcherOptions): CompiledMatcher;
|
|
412
|
+
declare function matchesAny(patterns: string[], opts?: MatcherOptions): CompiledMatcher;
|
|
413
|
+
|
|
414
|
+
declare class IdempotencyService {
|
|
415
|
+
private redis;
|
|
416
|
+
constructor(redis: Redis);
|
|
417
|
+
/**
|
|
418
|
+
* Attempts to acquire an idempotency lock on a WhatsApp message ID (wamid).
|
|
419
|
+
* @param wamid Unique WhatsApp message ID from the webhook payload.
|
|
420
|
+
* @param ttlSeconds Expiration time in seconds (default: 21600 - 6 hours).
|
|
421
|
+
* @returns Promise<boolean> True if the lock was acquired (first time), false if it is a duplicate.
|
|
422
|
+
*/
|
|
423
|
+
checkAndLock(wamid: string, ttlSeconds?: number): Promise<boolean>;
|
|
424
|
+
/**
|
|
425
|
+
* Releases/deletes the idempotency lock for a message ID.
|
|
426
|
+
* Useful to roll back locks when downstream operations (e.g. queue enqueue) fail.
|
|
427
|
+
*/
|
|
428
|
+
release(wamid: string): Promise<void>;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
declare class FlaresendSendError extends Error {
|
|
432
|
+
isAmbiguous: boolean;
|
|
433
|
+
statusCode?: number;
|
|
434
|
+
code?: string;
|
|
435
|
+
constructor(message: string, isAmbiguous: boolean, statusCode?: number, code?: string);
|
|
436
|
+
}
|
|
437
|
+
declare class KnownNotSentError extends FlaresendSendError {
|
|
438
|
+
constructor(message: string, statusCode?: number, code?: string);
|
|
439
|
+
}
|
|
440
|
+
declare class AmbiguousSendError extends FlaresendSendError {
|
|
441
|
+
constructor(message: string, statusCode?: number, code?: string);
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Classifies an error caught during Flaresend message transmission into
|
|
445
|
+
* a KnownNotSentError (rejected before send) or AmbiguousSendError (unconfirmed delivery status).
|
|
446
|
+
*/
|
|
447
|
+
declare function classifyFlaresendError(err: any): FlaresendSendError;
|
|
448
|
+
interface FlaresendClientOptions {
|
|
449
|
+
apiKey?: string;
|
|
450
|
+
baseUrl?: string;
|
|
451
|
+
mock?: boolean;
|
|
452
|
+
}
|
|
453
|
+
declare class FlaresendClient {
|
|
454
|
+
private sdk;
|
|
455
|
+
private isMock;
|
|
456
|
+
constructor(options?: FlaresendClientOptions);
|
|
457
|
+
/**
|
|
458
|
+
* Sends an outbound WhatsApp message to the recipient using the official Flaresend SDK.
|
|
459
|
+
* Categorizes any transmission errors into KnownNotSentError or AmbiguousSendError.
|
|
460
|
+
*/
|
|
461
|
+
sendMessage(to: string, text: string): Promise<{
|
|
462
|
+
success: boolean;
|
|
463
|
+
messageId?: string;
|
|
464
|
+
}>;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
interface NormalizedRequest {
|
|
468
|
+
body: any;
|
|
469
|
+
rawBody?: string;
|
|
470
|
+
headers: Record<string, string | string[] | undefined>;
|
|
471
|
+
}
|
|
472
|
+
interface NormalizedResponse {
|
|
473
|
+
status: number;
|
|
474
|
+
body: any;
|
|
475
|
+
}
|
|
476
|
+
interface WebhookJob {
|
|
477
|
+
wamid: string;
|
|
478
|
+
waUserId: string;
|
|
479
|
+
messageText: string;
|
|
480
|
+
timestamp: number;
|
|
481
|
+
}
|
|
482
|
+
interface WebhookQueue {
|
|
483
|
+
add(name: string, data: WebhookJob): Promise<any>;
|
|
484
|
+
}
|
|
485
|
+
interface WebhookCoreOptions {
|
|
486
|
+
idempotencyService: IdempotencyService;
|
|
487
|
+
queue: WebhookQueue;
|
|
488
|
+
webhookSecret?: string;
|
|
489
|
+
allowUnsigned?: boolean;
|
|
490
|
+
}
|
|
491
|
+
interface NormalizedMessage {
|
|
492
|
+
id: string;
|
|
493
|
+
from: string;
|
|
494
|
+
text: string;
|
|
495
|
+
timestamp: number;
|
|
496
|
+
}
|
|
497
|
+
interface NormalizedStatus {
|
|
498
|
+
id: string;
|
|
499
|
+
status: string;
|
|
500
|
+
recipient_id: string;
|
|
501
|
+
timestamp: number;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Robust utility to parse both flat Flaresend payloads and nested Meta/WhatsApp Cloud API payloads.
|
|
505
|
+
*/
|
|
506
|
+
declare function extractWebhookPayload(body: any): {
|
|
507
|
+
messages: NormalizedMessage[];
|
|
508
|
+
statuses: NormalizedStatus[];
|
|
509
|
+
};
|
|
510
|
+
/**
|
|
511
|
+
* Validates HMAC-SHA256 signatures of webhook payloads.
|
|
512
|
+
*
|
|
513
|
+
* PROVISIONAL IMPLEMENTATION NOTE:
|
|
514
|
+
* The Flaresend SDK contains a provisional signature validation placeholder helper:
|
|
515
|
+
* `const isValid = flaresend.webhooks.validateSignature(JSON.stringify(req.body), signatureHeader, secret);`
|
|
516
|
+
*
|
|
517
|
+
* However, since Flaresend has not officially finalized or published the exact payload/HMAC scheme details,
|
|
518
|
+
* we implement this robust, timing-safe validation. It covers standard Meta-style `X-Hub-Signature-256`
|
|
519
|
+
* signatures (since Flaresend likely proxies standard WhatsApp Cloud API webhooks) and custom `X-Flaresend-Signature` headers.
|
|
520
|
+
* Once Flaresend publishes exact specifications for their SDK's `validateSignature`, this wrapper can delegate
|
|
521
|
+
* directly to it.
|
|
522
|
+
*/
|
|
523
|
+
declare function verifyWebhookSignature(rawBody: string, signatureHeader: string | undefined, secret: string): boolean;
|
|
524
|
+
/**
|
|
525
|
+
* Framework-agnostic core webhook handler.
|
|
526
|
+
* Validates request signature, extracts messages/statuses, runs idempotency checks,
|
|
527
|
+
* and enqueues new messages as BullMQ jobs.
|
|
528
|
+
*/
|
|
529
|
+
declare function handleWebhookCore(req: NormalizedRequest, options: WebhookCoreOptions): Promise<NormalizedResponse>;
|
|
530
|
+
declare function createFastifyAdapter(options: WebhookCoreOptions): (request: any, reply: any) => Promise<any>;
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Fastify route handler adapter for the framework-agnostic webhook handler core.
|
|
534
|
+
* Usage:
|
|
535
|
+
* fastify.post("/webhook", fastifyAdapter(options));
|
|
536
|
+
*/
|
|
537
|
+
declare function fastifyAdapter(options: WebhookCoreOptions): (request: any, reply: any) => Promise<any>;
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Express middleware / route handler adapter wrapping handleWebhookCore.
|
|
541
|
+
* Usage:
|
|
542
|
+
* app.post("/webhook", expressAdapter(options));
|
|
543
|
+
*/
|
|
544
|
+
declare function createExpressAdapter(options: WebhookCoreOptions): (req: any, res: any) => Promise<void>;
|
|
545
|
+
declare function expressAdapter(options: WebhookCoreOptions): (req: any, res: any) => Promise<void>;
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Raw Node.js HTTP server adapter for the framework-agnostic webhook handler core.
|
|
549
|
+
* Usage:
|
|
550
|
+
* import { rawHttpAdapter } from "@henry-dev/botkit/http";
|
|
551
|
+
* http.createServer(rawHttpAdapter(options)).listen(3000);
|
|
552
|
+
*/
|
|
553
|
+
declare function createRawHttpAdapter(options: WebhookCoreOptions): (req: any, res: any) => Promise<void>;
|
|
554
|
+
declare function rawHttpAdapter(options: WebhookCoreOptions): (req: any, res: any) => Promise<void>;
|
|
555
|
+
declare const httpAdapter: typeof rawHttpAdapter;
|
|
556
|
+
|
|
557
|
+
declare class ConcurrencyLockError extends Error {
|
|
558
|
+
waUserId: string;
|
|
559
|
+
constructor(waUserId: string, message?: string);
|
|
560
|
+
}
|
|
561
|
+
interface ConcurrencyLease {
|
|
562
|
+
readonly leaseKey: string;
|
|
563
|
+
readonly token: string;
|
|
564
|
+
readonly waUserId: string;
|
|
565
|
+
readonly acquiredAt: number;
|
|
566
|
+
readonly ttlMs: number;
|
|
567
|
+
release(): Promise<boolean>;
|
|
568
|
+
}
|
|
569
|
+
interface ConcurrencyLeaseOptions {
|
|
570
|
+
ttlMs?: number;
|
|
571
|
+
acquireTimeoutMs?: number;
|
|
572
|
+
retryIntervalMs?: number;
|
|
573
|
+
keyPrefix?: string;
|
|
574
|
+
}
|
|
575
|
+
interface ConcurrencyLeaseService {
|
|
576
|
+
acquire(waUserId: string, options?: ConcurrencyLeaseOptions): Promise<ConcurrencyLease>;
|
|
577
|
+
isLocked(waUserId: string): Promise<boolean>;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* In-memory lease service for unit testing, development, and single-instance deployments.
|
|
581
|
+
*/
|
|
582
|
+
declare class InMemoryConcurrencyLeaseService implements ConcurrencyLeaseService {
|
|
583
|
+
private locks;
|
|
584
|
+
acquire(waUserId: string, options?: ConcurrencyLeaseOptions): Promise<ConcurrencyLease>;
|
|
585
|
+
isLocked(waUserId: string, keyPrefix?: string): Promise<boolean>;
|
|
586
|
+
clear(): void;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Production Redis distributed lock using SET NX PX and atomic Lua unlock script.
|
|
590
|
+
*/
|
|
591
|
+
declare class RedisConcurrencyLeaseService implements ConcurrencyLeaseService {
|
|
592
|
+
private redisClient;
|
|
593
|
+
private static readonly UNLOCK_SCRIPT;
|
|
594
|
+
constructor(redisClient: any);
|
|
595
|
+
acquire(waUserId: string, options?: ConcurrencyLeaseOptions): Promise<ConcurrencyLease>;
|
|
596
|
+
isLocked(waUserId: string, keyPrefix?: string): Promise<boolean>;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Factory helper to construct the appropriate lease service based on environment or connection.
|
|
600
|
+
*/
|
|
601
|
+
declare function createConcurrencyLeaseService(connection?: any): ConcurrencyLeaseService;
|
|
602
|
+
|
|
603
|
+
interface FSMWorkerOptions {
|
|
604
|
+
connection: any;
|
|
605
|
+
queueName?: string;
|
|
606
|
+
resendQueueName?: string;
|
|
607
|
+
persistence: PersistenceAdapter;
|
|
608
|
+
flow: FlowDefinition;
|
|
609
|
+
flaresendClient: FlaresendClient;
|
|
610
|
+
concurrency?: number;
|
|
611
|
+
leaseService?: ConcurrencyLeaseService;
|
|
612
|
+
leaseOptions?: ConcurrencyLeaseOptions;
|
|
613
|
+
enableConcurrencyLease?: boolean;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Decoupled job processor function containing the core worker logic.
|
|
617
|
+
* This makes the FSM Worker extremely easy to test in isolation without requiring actual Redis.
|
|
618
|
+
*/
|
|
619
|
+
declare function processFSMJob(jobData: {
|
|
620
|
+
wamid: string;
|
|
621
|
+
waUserId: string;
|
|
622
|
+
messageText: string;
|
|
623
|
+
timestamp?: number;
|
|
624
|
+
}, options: {
|
|
625
|
+
persistence: PersistenceAdapter;
|
|
626
|
+
flow: FlowDefinition;
|
|
627
|
+
flaresendClient: FlaresendClient;
|
|
628
|
+
resendQueue?: Queue;
|
|
629
|
+
leaseService?: ConcurrencyLeaseService;
|
|
630
|
+
leaseOptions?: ConcurrencyLeaseOptions;
|
|
631
|
+
}): Promise<{
|
|
632
|
+
nextState: any;
|
|
633
|
+
sentReply: boolean;
|
|
634
|
+
result: TransitionResult;
|
|
635
|
+
}>;
|
|
636
|
+
/**
|
|
637
|
+
* Creates and registers a BullMQ Worker instance connected to the specified Redis connection.
|
|
638
|
+
* Also configures a companion resend queue and worker to gracefully handle delivery failures.
|
|
639
|
+
*/
|
|
640
|
+
declare function createFSMWorker(options: FSMWorkerOptions): Worker;
|
|
641
|
+
|
|
642
|
+
interface TurnTrace {
|
|
643
|
+
id: string;
|
|
644
|
+
sessionId: string;
|
|
645
|
+
timestamp: number;
|
|
646
|
+
flowId: string;
|
|
647
|
+
fromNodeId: string;
|
|
648
|
+
toNodeId: string;
|
|
649
|
+
rawInput: string;
|
|
650
|
+
matchedPattern: string;
|
|
651
|
+
outboundText: string | null;
|
|
652
|
+
retryCount: number;
|
|
653
|
+
status: "active" | "escalated" | "completed" | "abandoned";
|
|
654
|
+
contextSnapshot: Record<string, any>;
|
|
655
|
+
durationMs: number;
|
|
656
|
+
}
|
|
657
|
+
interface SessionTrace {
|
|
658
|
+
sessionId: string;
|
|
659
|
+
flowId: string;
|
|
660
|
+
startTime: number;
|
|
661
|
+
endTime: number;
|
|
662
|
+
turns: TurnTrace[];
|
|
663
|
+
finalStatus: "active" | "escalated" | "completed" | "abandoned";
|
|
664
|
+
totalRetries: number;
|
|
665
|
+
}
|
|
666
|
+
interface FlowMetrics {
|
|
667
|
+
totalSessions: number;
|
|
668
|
+
completedSessions: number;
|
|
669
|
+
escalatedSessions: number;
|
|
670
|
+
activeSessions: number;
|
|
671
|
+
containmentRate: number;
|
|
672
|
+
completionRate: number;
|
|
673
|
+
escalationRate: number;
|
|
674
|
+
avgTurnsPerSession: number;
|
|
675
|
+
totalRetries: number;
|
|
676
|
+
nodeStats: Record<string, {
|
|
677
|
+
visits: number;
|
|
678
|
+
retries: number;
|
|
679
|
+
dropOffs: number;
|
|
680
|
+
unhandledInputs: string[];
|
|
681
|
+
patternBreakdown: Record<string, number>;
|
|
682
|
+
}>;
|
|
683
|
+
}
|
|
684
|
+
declare class TraceCollector {
|
|
685
|
+
private traces;
|
|
686
|
+
private listeners;
|
|
687
|
+
constructor(initialTraces?: TurnTrace[]);
|
|
688
|
+
logTurn(turn: Omit<TurnTrace, "id" | "timestamp">): TurnTrace;
|
|
689
|
+
onTurn(listener: (turn: TurnTrace) => void): () => void;
|
|
690
|
+
getTraces(): TurnTrace[];
|
|
691
|
+
getSessions(): SessionTrace[];
|
|
692
|
+
computeMetrics(flow?: FlowDefinition): FlowMetrics;
|
|
693
|
+
clear(): void;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
interface ScenarioTurnStep {
|
|
697
|
+
input: string;
|
|
698
|
+
expectedNodeId?: string;
|
|
699
|
+
expectedOutboundPattern?: string | RegExp;
|
|
700
|
+
expectedContext?: Record<string, any>;
|
|
701
|
+
expectEscalated?: boolean;
|
|
702
|
+
expectCompleted?: boolean;
|
|
703
|
+
}
|
|
704
|
+
interface ConversationScenario {
|
|
705
|
+
id: string;
|
|
706
|
+
name: string;
|
|
707
|
+
description?: string;
|
|
708
|
+
initialContext?: Record<string, any>;
|
|
709
|
+
steps: ScenarioTurnStep[];
|
|
710
|
+
}
|
|
711
|
+
interface StepEvaluationResult {
|
|
712
|
+
stepIndex: number;
|
|
713
|
+
input: string;
|
|
714
|
+
actualNodeId: string;
|
|
715
|
+
expectedNodeId?: string;
|
|
716
|
+
actualOutbound: string | null;
|
|
717
|
+
expectedOutboundPattern?: string | RegExp;
|
|
718
|
+
actualStatus: string;
|
|
719
|
+
actualContext: Record<string, any>;
|
|
720
|
+
passed: boolean;
|
|
721
|
+
failureReason?: string;
|
|
722
|
+
}
|
|
723
|
+
interface ScenarioResult {
|
|
724
|
+
scenarioId: string;
|
|
725
|
+
scenarioName: string;
|
|
726
|
+
passed: boolean;
|
|
727
|
+
stepResults: StepEvaluationResult[];
|
|
728
|
+
durationMs: number;
|
|
729
|
+
visitedNodeIds: string[];
|
|
730
|
+
}
|
|
731
|
+
interface EvaluationReport {
|
|
732
|
+
totalScenarios: number;
|
|
733
|
+
passedScenarios: number;
|
|
734
|
+
failedScenarios: number;
|
|
735
|
+
passRate: number;
|
|
736
|
+
nodeCoveragePercentage: number;
|
|
737
|
+
coveredNodeIds: string[];
|
|
738
|
+
uncoveredNodeIds: string[];
|
|
739
|
+
scenarioResults: ScenarioResult[];
|
|
740
|
+
totalDurationMs: number;
|
|
741
|
+
}
|
|
742
|
+
declare class Evaluator {
|
|
743
|
+
private flow;
|
|
744
|
+
constructor(flow: FlowDefinition);
|
|
745
|
+
runScenario(scenario: ConversationScenario): Promise<ScenarioResult>;
|
|
746
|
+
runSuite(scenarios: ConversationScenario[]): Promise<EvaluationReport>;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
export { type ActionBranch, type ActionDefinition, type ActionDefinitionOptions, type ActionFunction, ActionNodeBuilder, AmbiguousSendError, type AvailableCommandsResult, Bot, CallbackPersistenceAdapter, type CallbackPersistenceAdapterOptions, CompileError, type CompiledMatcher, type ConcurrencyLease, type ConcurrencyLeaseOptions, type ConcurrencyLeaseService, ConcurrencyLockError, Context, type ConversationScenario, type ConversationState, type EvaluationReport, Evaluator, FSMEngine, type FSMWorkerOptions, FlaresendClient, type FlaresendClientOptions, FlaresendSendError, type FlowDefinition, type FlowMetrics, type FlowNode, FlowNodeBuilder, type GlobalTransition, GlobalTransitionBuilder, type GlobalTransitionOptions, type HandoffDefinition, HandoffNodeBuilder, type HandoffNodeOptions, type HandoffSession, IdempotencyService, InMemoryConcurrencyLeaseService, KnownNotSentError, type LegalCommandInfo, type LifecycleEventInfo, type LifecycleHook, type MatchResult, MongoosePersistenceAdapter, type NormalizedRequest, type NormalizedResponse, OptimisticConcurrencyError, type PersistenceAdapter, RedisConcurrencyLeaseService, type ScenarioResult, type ScenarioTurnStep, type SessionTrace, type StepEvaluationResult, TraceCollector, type Transition, type TransitionResult, type TurnTrace, type WebhookCoreOptions, type WebhookJob, type WebhookQueue, classifyFlaresendError, createConcurrencyLeaseService, createExpressAdapter, createFSMWorker, createFastifyAdapter, createLiteralMatcher, createRawHttpAdapter, expressAdapter, extractWebhookPayload, fastifyAdapter, handleWebhookCore, httpAdapter, matchesAny, processFSMJob, rawHttpAdapter, registerFlowDefinition, runConversationStep, verifyWebhookSignature };
|