@builderbot/manager 1.0.0 → 1.3.14-alpha.148

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 CHANGED
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- var require$$1$5 = require('path');
4
3
  var require$$9 = require('console');
5
4
  var require$$0$9 = require('fs');
6
5
  var require$$11 = require('node:events');
@@ -13,6 +12,7 @@ var require$$17 = require('assert');
13
12
  var require$$18 = require('tty');
14
13
  var require$$1$6 = require('util');
15
14
  var require$$0$a = require('os');
15
+ var require$$1$5 = require('path');
16
16
  var require$$2$3 = require('events');
17
17
  var require$$0$b = require('child_process');
18
18
  var require$$24 = require('net');
@@ -39540,15 +39540,15 @@ class BotManager {
39540
39540
  enabled: config.autoReconnect?.enabled ?? true,
39541
39541
  maxRetries: config.autoReconnect?.maxRetries ?? 5,
39542
39542
  initialDelay: config.autoReconnect?.initialDelay ?? 1000,
39543
- maxDelay: config.autoReconnect?.maxDelay ?? 30000
39544
- }
39543
+ maxDelay: config.autoReconnect?.maxDelay ?? 30000,
39544
+ },
39545
39545
  };
39546
39546
  }
39547
39547
  /**
39548
39548
  * Create a new bot instance for a tenant
39549
39549
  */
39550
39550
  async createBot(tenantConfig) {
39551
- const { tenantId, name, flows, port, providerOptions = {}, databaseOptions = {}, providerClass, databaseClass } = tenantConfig;
39551
+ const { tenantId, name, flows, port, providerOptions = {}, databaseOptions = {}, providerClass, databaseClass, } = tenantConfig;
39552
39552
  if (this.bots.has(tenantId)) {
39553
39553
  throw new Error(`Bot with tenantId "${tenantId}" already exists`);
39554
39554
  }
@@ -39566,13 +39566,13 @@ class BotManager {
39566
39566
  const adapterProvider = createProvider_1(ProviderToUse, {
39567
39567
  ...this.config.defaultProviderOptions,
39568
39568
  ...providerOptions,
39569
- name: sessionPath // This creates isolated session folder per tenant
39569
+ name: sessionPath, // This creates isolated session folder per tenant
39570
39570
  });
39571
39571
  // Use tenant-specific or default database class
39572
39572
  const DatabaseToUse = databaseClass || this.config.defaultDatabaseClass || MemoryDB_1;
39573
39573
  const adapterDB = new DatabaseToUse({
39574
39574
  ...this.config.defaultDatabaseOptions,
39575
- ...databaseOptions
39575
+ ...databaseOptions,
39576
39576
  });
39577
39577
  const { handleCtx, httpServer } = await createBot_1({
39578
39578
  flow: adapterFlow,
@@ -39591,7 +39591,7 @@ class BotManager {
39591
39591
  createdAt: new Date(),
39592
39592
  status: 'initializing',
39593
39593
  providerType: ProviderToUse.name,
39594
- databaseType: DatabaseToUse.name
39594
+ databaseType: DatabaseToUse.name,
39595
39595
  };
39596
39596
  this.bots.set(tenantId, botInstance);
39597
39597
  // Initialize reconnect state
@@ -39599,7 +39599,7 @@ class BotManager {
39599
39599
  attempts: 0,
39600
39600
  lastAttempt: new Date(),
39601
39601
  nextDelay: this.config.autoReconnect.initialDelay,
39602
- isReconnecting: false
39602
+ isReconnecting: false,
39603
39603
  });
39604
39604
  // Setup provider event listeners for real status tracking
39605
39605
  this.setupProviderEvents(tenantId, adapterProvider);
@@ -39612,7 +39612,7 @@ class BotManager {
39612
39612
  port,
39613
39613
  sessionPath,
39614
39614
  providerType: botInstance.providerType,
39615
- databaseType: botInstance.databaseType
39615
+ databaseType: botInstance.databaseType,
39616
39616
  });
39617
39617
  return botInstance;
39618
39618
  }
@@ -39668,7 +39668,7 @@ class BotManager {
39668
39668
  const { maxRetries, maxDelay } = this.config.autoReconnect;
39669
39669
  if (reconnectState.attempts >= maxRetries) {
39670
39670
  this.emit('bot:error', tenantId, {
39671
- error: new Error(`Max reconnection attempts (${maxRetries}) reached`)
39671
+ error: new Error(`Max reconnection attempts (${maxRetries}) reached`),
39672
39672
  });
39673
39673
  return;
39674
39674
  }
@@ -39678,7 +39678,7 @@ class BotManager {
39678
39678
  this.emit('bot:reconnecting', tenantId, {
39679
39679
  attempt: reconnectState.attempts,
39680
39680
  maxRetries,
39681
- nextDelay: reconnectState.nextDelay
39681
+ nextDelay: reconnectState.nextDelay,
39682
39682
  });
39683
39683
  // Wait before reconnecting
39684
39684
  await this.delay(reconnectState.nextDelay);
@@ -39696,7 +39696,7 @@ class BotManager {
39696
39696
  reconnectState.nextDelay = Math.min(reconnectState.nextDelay * 2, maxDelay);
39697
39697
  this.emit('bot:error', tenantId, {
39698
39698
  error,
39699
- reconnectAttempt: reconnectState.attempts
39699
+ reconnectAttempt: reconnectState.attempts,
39700
39700
  });
39701
39701
  // Try again
39702
39702
  this.attemptReconnect(tenantId);
@@ -39706,7 +39706,7 @@ class BotManager {
39706
39706
  * Helper delay function
39707
39707
  */
39708
39708
  delay(ms) {
39709
- return new Promise(resolve => setTimeout(resolve, ms));
39709
+ return new Promise((resolve) => setTimeout(resolve, ms));
39710
39710
  }
39711
39711
  /**
39712
39712
  * Get a bot instance by tenant ID
@@ -39766,7 +39766,7 @@ class BotManager {
39766
39766
  */
39767
39767
  async shutdown() {
39768
39768
  const tenantIds = this.listBots();
39769
- await Promise.all(tenantIds.map(tenantId => this.removeBot(tenantId)));
39769
+ await Promise.all(tenantIds.map((tenantId) => this.removeBot(tenantId)));
39770
39770
  }
39771
39771
  /**
39772
39772
  * Register an event handler
@@ -39792,7 +39792,7 @@ class BotManager {
39792
39792
  emit(event, tenantId, data) {
39793
39793
  const handlers = this.eventHandlers.get(event);
39794
39794
  if (handlers) {
39795
- handlers.forEach(handler => {
39795
+ handlers.forEach((handler) => {
39796
39796
  try {
39797
39797
  handler(tenantId, data);
39798
39798
  }
@@ -39836,7 +39836,7 @@ class BotManager {
39836
39836
  attempts: 0,
39837
39837
  lastAttempt: new Date(),
39838
39838
  nextDelay: this.config.autoReconnect.initialDelay,
39839
- isReconnecting: false
39839
+ isReconnecting: false,
39840
39840
  });
39841
39841
  try {
39842
39842
  await this.removeBot(tenantId);
@@ -39861,7 +39861,7 @@ class BotManager {
39861
39861
  tenantId,
39862
39862
  name: existingBot?.name || tenantId,
39863
39863
  flows: [],
39864
- port: existingBot?.port
39864
+ port: existingBot?.port,
39865
39865
  };
39866
39866
  // Remove existing bot
39867
39867
  await this.removeBot(tenantId);
@@ -39876,7 +39876,7 @@ class BotManager {
39876
39876
  * Get summary info of all bots (useful for dashboards)
39877
39877
  */
39878
39878
  getBotsInfo() {
39879
- return this.getAllBots().map(bot => ({
39879
+ return this.getAllBots().map((bot) => ({
39880
39880
  tenantId: bot.tenantId,
39881
39881
  name: bot.name,
39882
39882
  status: bot.status,
@@ -39885,7 +39885,7 @@ class BotManager {
39885
39885
  uptime: Date.now() - bot.createdAt.getTime(),
39886
39886
  providerType: bot.providerType,
39887
39887
  databaseType: bot.databaseType,
39888
- reconnectState: this.reconnectStates.get(bot.tenantId)
39888
+ reconnectState: this.reconnectStates.get(bot.tenantId),
39889
39889
  }));
39890
39890
  }
39891
39891
  /**
@@ -39898,7 +39898,7 @@ class BotManager {
39898
39898
  connected: 0,
39899
39899
  disconnected: 0,
39900
39900
  error: 0,
39901
- initializing: 0
39901
+ initializing: 0,
39902
39902
  };
39903
39903
  for (const bot of bots) {
39904
39904
  statusCounts[bot.status]++;
@@ -39914,7 +39914,7 @@ class BotManager {
39914
39914
  status,
39915
39915
  bots: statusCounts,
39916
39916
  memory: process.memoryUsage(),
39917
- uptime: process.uptime()
39917
+ uptime: process.uptime(),
39918
39918
  };
39919
39919
  }
39920
39920
  /**
@@ -39936,6 +39936,324 @@ class BotManager {
39936
39936
  }
39937
39937
  }
39938
39938
 
39939
+ /**
39940
+ * FlowRegistry manages flow definitions that can be used when creating bots
39941
+ */
39942
+ class FlowRegistry {
39943
+ constructor() {
39944
+ this.flows = new Map();
39945
+ }
39946
+ /**
39947
+ * Register a programmatic flow (created with addKeyword)
39948
+ */
39949
+ register(id, name, flow) {
39950
+ const definition = {
39951
+ id,
39952
+ name,
39953
+ flow,
39954
+ dynamic: false,
39955
+ createdAt: new Date(),
39956
+ updatedAt: new Date(),
39957
+ };
39958
+ this.flows.set(id, definition);
39959
+ return definition;
39960
+ }
39961
+ /**
39962
+ * Register a dynamic flow from JSON configuration
39963
+ */
39964
+ registerDynamic(config) {
39965
+ const { id, name, keyword, steps } = config;
39966
+ const flow = this.buildFlowFromSteps(keyword, steps);
39967
+ const definition = {
39968
+ id,
39969
+ name,
39970
+ flow,
39971
+ dynamic: true,
39972
+ config,
39973
+ createdAt: new Date(),
39974
+ updatedAt: new Date(),
39975
+ };
39976
+ this.flows.set(id, definition);
39977
+ return definition;
39978
+ }
39979
+ /**
39980
+ * Update a dynamic flow
39981
+ */
39982
+ update(id, updates) {
39983
+ const existing = this.flows.get(id);
39984
+ if (!existing || !existing.dynamic || !existing.config) {
39985
+ return null;
39986
+ }
39987
+ // Merge updates with existing config
39988
+ const newConfig = {
39989
+ ...existing.config,
39990
+ ...(updates.name && { name: updates.name }),
39991
+ ...(updates.keyword && { keyword: updates.keyword }),
39992
+ ...(updates.steps && { steps: updates.steps }),
39993
+ };
39994
+ // Rebuild flow
39995
+ const flow = this.buildFlowFromSteps(newConfig.keyword, newConfig.steps);
39996
+ const definition = {
39997
+ id,
39998
+ name: newConfig.name,
39999
+ flow,
40000
+ dynamic: true,
40001
+ config: newConfig,
40002
+ createdAt: existing.createdAt,
40003
+ updatedAt: new Date(),
40004
+ };
40005
+ this.flows.set(id, definition);
40006
+ return definition;
40007
+ }
40008
+ /**
40009
+ * Remove a flow from registry
40010
+ */
40011
+ remove(id) {
40012
+ return this.flows.delete(id);
40013
+ }
40014
+ /**
40015
+ * Get a flow by ID
40016
+ */
40017
+ get(id) {
40018
+ return this.flows.get(id);
40019
+ }
40020
+ /**
40021
+ * Get all registered flows
40022
+ */
40023
+ getAll() {
40024
+ return Array.from(this.flows.values());
40025
+ }
40026
+ /**
40027
+ * Check if a flow exists
40028
+ */
40029
+ has(id) {
40030
+ return this.flows.has(id);
40031
+ }
40032
+ /**
40033
+ * Get all flow IDs
40034
+ */
40035
+ getIds() {
40036
+ return Array.from(this.flows.keys());
40037
+ }
40038
+ /**
40039
+ * Get count of registered flows
40040
+ */
40041
+ count() {
40042
+ return this.flows.size;
40043
+ }
40044
+ /**
40045
+ * Clear all flows
40046
+ */
40047
+ clear() {
40048
+ this.flows.clear();
40049
+ }
40050
+ /**
40051
+ * Get flows by type (dynamic or programmatic)
40052
+ */
40053
+ getByType(dynamic) {
40054
+ return this.getAll().filter((f) => f.dynamic === dynamic);
40055
+ }
40056
+ /**
40057
+ * Resolve multiple flow IDs to Flow objects
40058
+ */
40059
+ resolveFlows(flowIds) {
40060
+ const flows = [];
40061
+ const missing = [];
40062
+ for (const id of flowIds) {
40063
+ const definition = this.flows.get(id);
40064
+ if (definition) {
40065
+ flows.push(definition.flow);
40066
+ }
40067
+ else {
40068
+ missing.push(id);
40069
+ }
40070
+ }
40071
+ return { flows, missing };
40072
+ }
40073
+ /**
40074
+ * Build a flow from steps configuration
40075
+ */
40076
+ buildFlowFromSteps(keyword, steps) {
40077
+ const keywords = Array.isArray(keyword)
40078
+ ? keyword
40079
+ : keyword;
40080
+ let flow = addKeyword_1(keywords);
40081
+ for (const step of steps) {
40082
+ const options = {};
40083
+ if (step.delay)
40084
+ options.delay = step.delay;
40085
+ if (step.media)
40086
+ options.media = step.media;
40087
+ if (step.capture)
40088
+ options.capture = step.capture;
40089
+ flow = flow.addAnswer(step.answer, Object.keys(options).length > 0 ? options : undefined);
40090
+ }
40091
+ return flow;
40092
+ }
40093
+ /**
40094
+ * Export all dynamic flows as serializable configs
40095
+ */
40096
+ exportDynamicFlows() {
40097
+ return this.getByType(true)
40098
+ .filter((f) => f.config)
40099
+ .map((f) => f.config);
40100
+ }
40101
+ /**
40102
+ * Import dynamic flows from configs
40103
+ */
40104
+ importDynamicFlows(configs) {
40105
+ const failed = [];
40106
+ let imported = 0;
40107
+ for (const config of configs) {
40108
+ try {
40109
+ if (!this.has(config.id)) {
40110
+ this.registerDynamic(config);
40111
+ imported++;
40112
+ }
40113
+ }
40114
+ catch {
40115
+ failed.push(config.id);
40116
+ }
40117
+ }
40118
+ return { imported, failed };
40119
+ }
40120
+ }
40121
+
40122
+ /**
40123
+ * Simple in-memory rate limiter
40124
+ */
40125
+ class RateLimiter {
40126
+ constructor(config = {}) {
40127
+ this.store = new Map();
40128
+ this.cleanupInterval = null;
40129
+ this.config = {
40130
+ maxRequests: config.maxRequests ?? 100,
40131
+ windowMs: config.windowMs ?? 60000, // 1 minute
40132
+ message: config.message ?? 'Too many requests, please try again later',
40133
+ skipPaths: config.skipPaths ?? ['/docs', '/api/health'],
40134
+ keyExtractor: config.keyExtractor ?? this.defaultKeyExtractor,
40135
+ };
40136
+ // Cleanup expired entries every minute
40137
+ this.cleanupInterval = setInterval(() => this.cleanup(), 60000);
40138
+ }
40139
+ /**
40140
+ * Default key extractor - uses IP address
40141
+ */
40142
+ defaultKeyExtractor(req) {
40143
+ const forwarded = req.headers['x-forwarded-for'];
40144
+ if (forwarded) {
40145
+ const ips = Array.isArray(forwarded) ? forwarded[0] : forwarded.split(',')[0];
40146
+ return ips.trim();
40147
+ }
40148
+ return req.socket?.remoteAddress || 'unknown';
40149
+ }
40150
+ /**
40151
+ * Check if request should be rate limited
40152
+ */
40153
+ isRateLimited(req) {
40154
+ const url = req.url || '/';
40155
+ // Skip certain paths
40156
+ if (this.config.skipPaths.some((path) => url.startsWith(path))) {
40157
+ return { limited: false, remaining: this.config.maxRequests, resetTime: 0 };
40158
+ }
40159
+ const key = this.config.keyExtractor(req);
40160
+ const now = Date.now();
40161
+ let entry = this.store.get(key);
40162
+ // If no entry or window expired, create new entry
40163
+ if (!entry || now > entry.resetTime) {
40164
+ entry = {
40165
+ count: 1,
40166
+ resetTime: now + this.config.windowMs,
40167
+ };
40168
+ this.store.set(key, entry);
40169
+ return {
40170
+ limited: false,
40171
+ remaining: this.config.maxRequests - 1,
40172
+ resetTime: entry.resetTime,
40173
+ };
40174
+ }
40175
+ // Increment count
40176
+ entry.count++;
40177
+ // Check if over limit
40178
+ if (entry.count > this.config.maxRequests) {
40179
+ return {
40180
+ limited: true,
40181
+ remaining: 0,
40182
+ resetTime: entry.resetTime,
40183
+ };
40184
+ }
40185
+ return {
40186
+ limited: false,
40187
+ remaining: this.config.maxRequests - entry.count,
40188
+ resetTime: entry.resetTime,
40189
+ };
40190
+ }
40191
+ /**
40192
+ * Create middleware function
40193
+ */
40194
+ middleware() {
40195
+ return (req, res, next) => {
40196
+ const result = this.isRateLimited(req);
40197
+ // Set rate limit headers
40198
+ res.setHeader('X-RateLimit-Limit', this.config.maxRequests.toString());
40199
+ res.setHeader('X-RateLimit-Remaining', result.remaining.toString());
40200
+ res.setHeader('X-RateLimit-Reset', result.resetTime.toString());
40201
+ if (result.limited) {
40202
+ res.setHeader('Retry-After', Math.ceil((result.resetTime - Date.now()) / 1000).toString());
40203
+ res.writeHead(429, { 'Content-Type': 'application/json' });
40204
+ res.end(JSON.stringify({
40205
+ error: this.config.message,
40206
+ retryAfter: Math.ceil((result.resetTime - Date.now()) / 1000),
40207
+ }));
40208
+ return;
40209
+ }
40210
+ next();
40211
+ };
40212
+ }
40213
+ /**
40214
+ * Reset rate limit for a specific key
40215
+ */
40216
+ reset(key) {
40217
+ this.store.delete(key);
40218
+ }
40219
+ /**
40220
+ * Clear all rate limit data
40221
+ */
40222
+ clear() {
40223
+ this.store.clear();
40224
+ }
40225
+ /**
40226
+ * Cleanup expired entries
40227
+ */
40228
+ cleanup() {
40229
+ const now = Date.now();
40230
+ for (const [key, entry] of this.store.entries()) {
40231
+ if (now > entry.resetTime) {
40232
+ this.store.delete(key);
40233
+ }
40234
+ }
40235
+ }
40236
+ /**
40237
+ * Stop the cleanup interval
40238
+ */
40239
+ destroy() {
40240
+ if (this.cleanupInterval) {
40241
+ clearInterval(this.cleanupInterval);
40242
+ this.cleanupInterval = null;
40243
+ }
40244
+ this.store.clear();
40245
+ }
40246
+ /**
40247
+ * Get current stats
40248
+ */
40249
+ getStats() {
40250
+ return {
40251
+ activeKeys: this.store.size,
40252
+ config: this.config,
40253
+ };
40254
+ }
40255
+ }
40256
+
39939
40257
  /**
39940
40258
  * Reserved tenant IDs that cannot be used
39941
40259
  */
@@ -39954,31 +40272,21 @@ const tenantIdSchema = zod.z
39954
40272
  */
39955
40273
  const createBotSchema = zod.z.object({
39956
40274
  tenantId: tenantIdSchema,
39957
- name: zod.z
39958
- .string()
39959
- .min(1, 'name cannot be empty')
39960
- .max(100, 'name must be 100 characters or less')
39961
- .optional(),
39962
- flowIds: zod.z
39963
- .array(zod.z.string().min(1, 'flowId cannot be empty'))
39964
- .min(1, 'At least one flowId is required'),
40275
+ name: zod.z.string().min(1, 'name cannot be empty').max(100, 'name must be 100 characters or less').optional(),
40276
+ flowIds: zod.z.array(zod.z.string().min(1, 'flowId cannot be empty')).min(1, 'At least one flowId is required'),
39965
40277
  port: zod.z
39966
40278
  .number()
39967
40279
  .int('port must be an integer')
39968
40280
  .min(1024, 'port must be 1024 or higher')
39969
40281
  .max(65535, 'port must be 65535 or lower')
39970
40282
  .optional(),
39971
- providerOptions: zod.z.record(zod.z.any()).optional()
40283
+ providerOptions: zod.z.record(zod.z.any()).optional(),
39972
40284
  });
39973
40285
  /**
39974
40286
  * Schema for updating a bot
39975
40287
  */
39976
40288
  const updateBotSchema = zod.z.object({
39977
- name: zod.z
39978
- .string()
39979
- .min(1, 'name cannot be empty')
39980
- .max(100, 'name must be 100 characters or less')
39981
- .optional()
40289
+ name: zod.z.string().min(1, 'name cannot be empty').max(100, 'name must be 100 characters or less').optional(),
39982
40290
  });
39983
40291
  /**
39984
40292
  * Schema for sending a message
@@ -39989,33 +40297,21 @@ const sendMessageSchema = zod.z.object({
39989
40297
  .min(10, 'number must be at least 10 characters')
39990
40298
  .max(20, 'number must be 20 characters or less')
39991
40299
  .regex(/^[0-9+]+$/, 'number can only contain digits and + symbol'),
39992
- message: zod.z
39993
- .string()
39994
- .min(1, 'message cannot be empty')
39995
- .max(4096, 'message must be 4096 characters or less'),
39996
- media: zod.z
39997
- .string()
39998
- .url('media must be a valid URL')
39999
- .optional()
40300
+ message: zod.z.string().min(1, 'message cannot be empty').max(4096, 'message must be 4096 characters or less'),
40301
+ media: zod.z.string().url('media must be a valid URL').optional(),
40000
40302
  });
40001
40303
  /**
40002
40304
  * Schema for restarting a bot
40003
40305
  */
40004
40306
  const restartBotSchema = zod.z.object({
40005
- flowIds: zod.z
40006
- .array(zod.z.string().min(1))
40007
- .min(1, 'At least one flowId is required'),
40307
+ flowIds: zod.z.array(zod.z.string().min(1)).min(1, 'At least one flowId is required'),
40008
40308
  port: zod.z
40009
40309
  .number()
40010
40310
  .int('port must be an integer')
40011
40311
  .min(1024, 'port must be 1024 or higher')
40012
40312
  .max(65535, 'port must be 65535 or lower')
40013
40313
  .optional(),
40014
- name: zod.z
40015
- .string()
40016
- .min(1)
40017
- .max(100)
40018
- .optional()
40314
+ name: zod.z.string().min(1).max(100).optional(),
40019
40315
  });
40020
40316
  /**
40021
40317
  * Reserved flow IDs
@@ -40032,7 +40328,7 @@ const flowStepSchema = zod.z.object({
40032
40328
  /** Optional media URL to attach */
40033
40329
  media: zod.z.string().url().optional(),
40034
40330
  /** Whether to capture user response */
40035
- capture: zod.z.boolean().optional()
40331
+ capture: zod.z.boolean().optional(),
40036
40332
  });
40037
40333
  /**
40038
40334
  * Schema for creating a dynamic flow
@@ -40048,23 +40344,17 @@ const createFlowSchema = zod.z.object({
40048
40344
  /** Display name for the flow */
40049
40345
  name: zod.z.string().min(1).max(100),
40050
40346
  /** Keywords that trigger this flow */
40051
- keyword: zod.z.union([
40052
- zod.z.string().min(1),
40053
- zod.z.array(zod.z.string().min(1)).min(1)
40054
- ]),
40347
+ keyword: zod.z.union([zod.z.string().min(1), zod.z.array(zod.z.string().min(1)).min(1)]),
40055
40348
  /** Steps/answers in the flow */
40056
- steps: zod.z.array(flowStepSchema).min(1, 'At least one step is required')
40349
+ steps: zod.z.array(flowStepSchema).min(1, 'At least one step is required'),
40057
40350
  });
40058
40351
  /**
40059
40352
  * Schema for updating a flow
40060
40353
  */
40061
40354
  const updateFlowSchema = zod.z.object({
40062
40355
  name: zod.z.string().min(1).max(100).optional(),
40063
- keyword: zod.z.union([
40064
- zod.z.string().min(1),
40065
- zod.z.array(zod.z.string().min(1)).min(1)
40066
- ]).optional(),
40067
- steps: zod.z.array(flowStepSchema).min(1).optional()
40356
+ keyword: zod.z.union([zod.z.string().min(1), zod.z.array(zod.z.string().min(1)).min(1)]).optional(),
40357
+ steps: zod.z.array(flowStepSchema).min(1).optional(),
40068
40358
  });
40069
40359
  /**
40070
40360
  * Validate data against a Zod schema
@@ -40074,17 +40364,17 @@ function validate(schema, data) {
40074
40364
  if (result.success) {
40075
40365
  return {
40076
40366
  success: true,
40077
- data: result.data
40367
+ data: result.data,
40078
40368
  };
40079
40369
  }
40080
40370
  const errors = result.error.errors.map((err) => ({
40081
40371
  field: err.path.join('.') || 'root',
40082
- message: err.message
40372
+ message: err.message,
40083
40373
  }));
40084
40374
  return {
40085
40375
  success: false,
40086
40376
  error: errors.map((e) => `${e.field}: ${e.message}`).join(', '),
40087
- errors
40377
+ errors,
40088
40378
  };
40089
40379
  }
40090
40380
 
@@ -40098,19 +40388,19 @@ const openApiSpec = {
40098
40388
  description: 'Multi-tenant WhatsApp Bot Manager REST API. Manage bots, flows, and send messages.',
40099
40389
  version: '1.0.0',
40100
40390
  contact: {
40101
- name: 'BotManager'
40102
- }
40391
+ name: 'BotManager',
40392
+ },
40103
40393
  },
40104
40394
  servers: [
40105
40395
  {
40106
40396
  url: '/api',
40107
- description: 'API Server'
40108
- }
40397
+ description: 'API Server',
40398
+ },
40109
40399
  ],
40110
40400
  tags: [
40111
40401
  { name: 'Health', description: 'Health check endpoints' },
40112
40402
  { name: 'Flows', description: 'Flow management' },
40113
- { name: 'Bots', description: 'Bot management' }
40403
+ { name: 'Bots', description: 'Bot management' },
40114
40404
  ],
40115
40405
  paths: {
40116
40406
  '/health': {
@@ -40128,14 +40418,14 @@ const openApiSpec = {
40128
40418
  properties: {
40129
40419
  status: { type: 'string', example: 'ok' },
40130
40420
  timestamp: { type: 'string', format: 'date-time' },
40131
- botsCount: { type: 'integer', example: 2 }
40132
- }
40133
- }
40134
- }
40135
- }
40136
- }
40137
- }
40138
- }
40421
+ botsCount: { type: 'integer', example: 2 },
40422
+ },
40423
+ },
40424
+ },
40425
+ },
40426
+ },
40427
+ },
40428
+ },
40139
40429
  },
40140
40430
  '/flows': {
40141
40431
  get: {
@@ -40153,14 +40443,14 @@ const openApiSpec = {
40153
40443
  count: { type: 'integer' },
40154
40444
  flows: {
40155
40445
  type: 'array',
40156
- items: { $ref: '#/components/schemas/FlowInfo' }
40157
- }
40158
- }
40159
- }
40160
- }
40161
- }
40162
- }
40163
- }
40446
+ items: { $ref: '#/components/schemas/FlowInfo' },
40447
+ },
40448
+ },
40449
+ },
40450
+ },
40451
+ },
40452
+ },
40453
+ },
40164
40454
  },
40165
40455
  post: {
40166
40456
  tags: ['Flows'],
@@ -40180,9 +40470,9 @@ const openApiSpec = {
40180
40470
  keyword: ['hola', 'hello', 'hi'],
40181
40471
  steps: [
40182
40472
  { answer: '👋 ¡Hola! Bienvenido', delay: 500 },
40183
- { answer: '¿En qué puedo ayudarte?', capture: true }
40184
- ]
40185
- }
40473
+ { answer: '¿En qué puedo ayudarte?', capture: true },
40474
+ ],
40475
+ },
40186
40476
  },
40187
40477
  support: {
40188
40478
  summary: 'Support flow',
@@ -40192,54 +40482,50 @@ const openApiSpec = {
40192
40482
  keyword: 'ayuda',
40193
40483
  steps: [
40194
40484
  { answer: '🆘 Soporte técnico' },
40195
- { answer: 'Por favor describe tu problema:', capture: true }
40196
- ]
40197
- }
40198
- }
40199
- }
40200
- }
40201
- }
40485
+ { answer: 'Por favor describe tu problema:', capture: true },
40486
+ ],
40487
+ },
40488
+ },
40489
+ },
40490
+ },
40491
+ },
40202
40492
  },
40203
40493
  responses: {
40204
40494
  '201': {
40205
40495
  description: 'Flow created successfully',
40206
40496
  content: {
40207
40497
  'application/json': {
40208
- schema: { $ref: '#/components/schemas/FlowResponse' }
40209
- }
40210
- }
40498
+ schema: { $ref: '#/components/schemas/FlowResponse' },
40499
+ },
40500
+ },
40211
40501
  },
40212
40502
  '400': { $ref: '#/components/responses/ValidationError' },
40213
- '409': { $ref: '#/components/responses/Conflict' }
40214
- }
40215
- }
40503
+ '409': { $ref: '#/components/responses/Conflict' },
40504
+ },
40505
+ },
40216
40506
  },
40217
40507
  '/flows/{flowId}': {
40218
40508
  get: {
40219
40509
  tags: ['Flows'],
40220
40510
  summary: 'Get flow by ID',
40221
- parameters: [
40222
- { $ref: '#/components/parameters/flowId' }
40223
- ],
40511
+ parameters: [{ $ref: '#/components/parameters/flowId' }],
40224
40512
  responses: {
40225
40513
  '200': {
40226
40514
  description: 'Flow details',
40227
40515
  content: {
40228
40516
  'application/json': {
40229
- schema: { $ref: '#/components/schemas/FlowInfo' }
40230
- }
40231
- }
40517
+ schema: { $ref: '#/components/schemas/FlowInfo' },
40518
+ },
40519
+ },
40232
40520
  },
40233
- '404': { $ref: '#/components/responses/NotFound' }
40234
- }
40521
+ '404': { $ref: '#/components/responses/NotFound' },
40522
+ },
40235
40523
  },
40236
40524
  put: {
40237
40525
  tags: ['Flows'],
40238
40526
  summary: 'Update a dynamic flow',
40239
40527
  description: 'Update an existing dynamic flow. Programmatic flows cannot be updated.',
40240
- parameters: [
40241
- { $ref: '#/components/parameters/flowId' }
40242
- ],
40528
+ parameters: [{ $ref: '#/components/parameters/flowId' }],
40243
40529
  requestBody: {
40244
40530
  required: true,
40245
40531
  content: {
@@ -40247,33 +40533,29 @@ const openApiSpec = {
40247
40533
  schema: { $ref: '#/components/schemas/UpdateFlow' },
40248
40534
  example: {
40249
40535
  name: 'Updated Flow Name',
40250
- steps: [
40251
- { answer: 'New message', delay: 1000 }
40252
- ]
40253
- }
40254
- }
40255
- }
40536
+ steps: [{ answer: 'New message', delay: 1000 }],
40537
+ },
40538
+ },
40539
+ },
40256
40540
  },
40257
40541
  responses: {
40258
40542
  '200': {
40259
40543
  description: 'Flow updated successfully',
40260
40544
  content: {
40261
40545
  'application/json': {
40262
- schema: { $ref: '#/components/schemas/FlowResponse' }
40263
- }
40264
- }
40546
+ schema: { $ref: '#/components/schemas/FlowResponse' },
40547
+ },
40548
+ },
40265
40549
  },
40266
40550
  '400': { $ref: '#/components/responses/ValidationError' },
40267
- '404': { $ref: '#/components/responses/NotFound' }
40268
- }
40551
+ '404': { $ref: '#/components/responses/NotFound' },
40552
+ },
40269
40553
  },
40270
40554
  delete: {
40271
40555
  tags: ['Flows'],
40272
40556
  summary: 'Delete a dynamic flow',
40273
40557
  description: 'Delete a dynamic flow. Programmatic flows cannot be deleted.',
40274
- parameters: [
40275
- { $ref: '#/components/parameters/flowId' }
40276
- ],
40558
+ parameters: [{ $ref: '#/components/parameters/flowId' }],
40277
40559
  responses: {
40278
40560
  '200': {
40279
40561
  description: 'Flow deleted successfully',
@@ -40283,16 +40565,16 @@ const openApiSpec = {
40283
40565
  type: 'object',
40284
40566
  properties: {
40285
40567
  message: { type: 'string' },
40286
- flowId: { type: 'string' }
40287
- }
40288
- }
40289
- }
40290
- }
40568
+ flowId: { type: 'string' },
40569
+ },
40570
+ },
40571
+ },
40572
+ },
40291
40573
  },
40292
40574
  '400': { $ref: '#/components/responses/BadRequest' },
40293
- '404': { $ref: '#/components/responses/NotFound' }
40294
- }
40295
- }
40575
+ '404': { $ref: '#/components/responses/NotFound' },
40576
+ },
40577
+ },
40296
40578
  },
40297
40579
  '/bots': {
40298
40580
  get: {
@@ -40309,14 +40591,14 @@ const openApiSpec = {
40309
40591
  count: { type: 'integer' },
40310
40592
  bots: {
40311
40593
  type: 'array',
40312
- items: { $ref: '#/components/schemas/BotInfo' }
40313
- }
40314
- }
40315
- }
40316
- }
40317
- }
40318
- }
40319
- }
40594
+ items: { $ref: '#/components/schemas/BotInfo' },
40595
+ },
40596
+ },
40597
+ },
40598
+ },
40599
+ },
40600
+ },
40601
+ },
40320
40602
  },
40321
40603
  post: {
40322
40604
  tags: ['Bots'],
@@ -40334,33 +40616,33 @@ const openApiSpec = {
40334
40616
  tenantId: 'my-bot',
40335
40617
  name: 'My WhatsApp Bot',
40336
40618
  flowIds: ['greeting', 'support'],
40337
- port: 3008
40338
- }
40619
+ port: 3008,
40620
+ },
40339
40621
  },
40340
40622
  minimalBot: {
40341
40623
  summary: 'Minimal bot',
40342
40624
  value: {
40343
40625
  tenantId: 'bot-2',
40344
- flowIds: ['greeting']
40345
- }
40346
- }
40347
- }
40348
- }
40349
- }
40626
+ flowIds: ['greeting'],
40627
+ },
40628
+ },
40629
+ },
40630
+ },
40631
+ },
40350
40632
  },
40351
40633
  responses: {
40352
40634
  '201': {
40353
40635
  description: 'Bot created successfully',
40354
40636
  content: {
40355
40637
  'application/json': {
40356
- schema: { $ref: '#/components/schemas/BotResponse' }
40357
- }
40358
- }
40638
+ schema: { $ref: '#/components/schemas/BotResponse' },
40639
+ },
40640
+ },
40359
40641
  },
40360
40642
  '400': { $ref: '#/components/responses/ValidationError' },
40361
- '409': { $ref: '#/components/responses/Conflict' }
40362
- }
40363
- }
40643
+ '409': { $ref: '#/components/responses/Conflict' },
40644
+ },
40645
+ },
40364
40646
  },
40365
40647
  '/bots/active': {
40366
40648
  get: {
@@ -40377,70 +40659,64 @@ const openApiSpec = {
40377
40659
  count: { type: 'integer' },
40378
40660
  bots: {
40379
40661
  type: 'array',
40380
- items: { $ref: '#/components/schemas/BotInfo' }
40381
- }
40382
- }
40383
- }
40384
- }
40385
- }
40386
- }
40387
- }
40388
- }
40662
+ items: { $ref: '#/components/schemas/BotInfo' },
40663
+ },
40664
+ },
40665
+ },
40666
+ },
40667
+ },
40668
+ },
40669
+ },
40670
+ },
40389
40671
  },
40390
40672
  '/bots/{tenantId}': {
40391
40673
  get: {
40392
40674
  tags: ['Bots'],
40393
40675
  summary: 'Get bot by tenant ID',
40394
- parameters: [
40395
- { $ref: '#/components/parameters/tenantId' }
40396
- ],
40676
+ parameters: [{ $ref: '#/components/parameters/tenantId' }],
40397
40677
  responses: {
40398
40678
  '200': {
40399
40679
  description: 'Bot details',
40400
40680
  content: {
40401
40681
  'application/json': {
40402
- schema: { $ref: '#/components/schemas/BotInfo' }
40403
- }
40404
- }
40682
+ schema: { $ref: '#/components/schemas/BotInfo' },
40683
+ },
40684
+ },
40405
40685
  },
40406
- '404': { $ref: '#/components/responses/NotFound' }
40407
- }
40686
+ '404': { $ref: '#/components/responses/NotFound' },
40687
+ },
40408
40688
  },
40409
40689
  put: {
40410
40690
  tags: ['Bots'],
40411
40691
  summary: 'Update bot',
40412
- parameters: [
40413
- { $ref: '#/components/parameters/tenantId' }
40414
- ],
40692
+ parameters: [{ $ref: '#/components/parameters/tenantId' }],
40415
40693
  requestBody: {
40416
40694
  required: true,
40417
40695
  content: {
40418
40696
  'application/json': {
40419
40697
  schema: { $ref: '#/components/schemas/UpdateBot' },
40420
40698
  example: {
40421
- name: 'Updated Bot Name'
40422
- }
40423
- }
40424
- }
40699
+ name: 'Updated Bot Name',
40700
+ },
40701
+ },
40702
+ },
40425
40703
  },
40426
40704
  responses: {
40427
40705
  '200': {
40428
40706
  description: 'Bot updated',
40429
40707
  content: {
40430
40708
  'application/json': {
40431
- schema: { $ref: '#/components/schemas/BotResponse' }
40432
- }
40433
- }
40709
+ schema: { $ref: '#/components/schemas/BotResponse' },
40710
+ },
40711
+ },
40434
40712
  },
40435
- '404': { $ref: '#/components/responses/NotFound' }
40436
- }
40713
+ '404': { $ref: '#/components/responses/NotFound' },
40714
+ },
40437
40715
  },
40438
40716
  delete: {
40439
40717
  tags: ['Bots'],
40440
40718
  summary: 'Delete bot',
40441
- parameters: [
40442
- { $ref: '#/components/parameters/tenantId' }
40443
- ],
40719
+ parameters: [{ $ref: '#/components/parameters/tenantId' }],
40444
40720
  responses: {
40445
40721
  '200': {
40446
40722
  description: 'Bot deleted successfully',
@@ -40450,24 +40726,22 @@ const openApiSpec = {
40450
40726
  type: 'object',
40451
40727
  properties: {
40452
40728
  message: { type: 'string' },
40453
- tenantId: { type: 'string' }
40454
- }
40455
- }
40456
- }
40457
- }
40729
+ tenantId: { type: 'string' },
40730
+ },
40731
+ },
40732
+ },
40733
+ },
40458
40734
  },
40459
- '404': { $ref: '#/components/responses/NotFound' }
40460
- }
40461
- }
40735
+ '404': { $ref: '#/components/responses/NotFound' },
40736
+ },
40737
+ },
40462
40738
  },
40463
40739
  '/bots/{tenantId}/qr': {
40464
40740
  get: {
40465
40741
  tags: ['Bots'],
40466
40742
  summary: 'Get QR code for bot',
40467
40743
  description: 'Get the QR code to connect WhatsApp. Returns null if already connected.',
40468
- parameters: [
40469
- { $ref: '#/components/parameters/tenantId' }
40470
- ],
40744
+ parameters: [{ $ref: '#/components/parameters/tenantId' }],
40471
40745
  responses: {
40472
40746
  '200': {
40473
40747
  description: 'QR code data',
@@ -40476,10 +40750,13 @@ const openApiSpec = {
40476
40750
  schema: {
40477
40751
  type: 'object',
40478
40752
  properties: {
40479
- status: { type: 'string', enum: ['initializing', 'connected', 'disconnected', 'error'] },
40753
+ status: {
40754
+ type: 'string',
40755
+ enum: ['initializing', 'connected', 'disconnected', 'error'],
40756
+ },
40480
40757
  qr: { type: 'string', nullable: true },
40481
- message: { type: 'string' }
40482
- }
40758
+ message: { type: 'string' },
40759
+ },
40483
40760
  },
40484
40761
  examples: {
40485
40762
  pending: {
@@ -40487,31 +40764,29 @@ const openApiSpec = {
40487
40764
  value: {
40488
40765
  status: 'initializing',
40489
40766
  qr: '2@abc123...',
40490
- message: 'Scan QR to connect'
40491
- }
40767
+ message: 'Scan QR to connect',
40768
+ },
40492
40769
  },
40493
40770
  connected: {
40494
40771
  summary: 'Already connected',
40495
40772
  value: {
40496
40773
  status: 'connected',
40497
- qr: null
40498
- }
40499
- }
40500
- }
40501
- }
40502
- }
40774
+ qr: null,
40775
+ },
40776
+ },
40777
+ },
40778
+ },
40779
+ },
40503
40780
  },
40504
- '404': { $ref: '#/components/responses/NotFound' }
40505
- }
40506
- }
40781
+ '404': { $ref: '#/components/responses/NotFound' },
40782
+ },
40783
+ },
40507
40784
  },
40508
40785
  '/bots/{tenantId}/restart': {
40509
40786
  post: {
40510
40787
  tags: ['Bots'],
40511
40788
  summary: 'Restart bot',
40512
- parameters: [
40513
- { $ref: '#/components/parameters/tenantId' }
40514
- ],
40789
+ parameters: [{ $ref: '#/components/parameters/tenantId' }],
40515
40790
  requestBody: {
40516
40791
  required: true,
40517
40792
  content: {
@@ -40519,10 +40794,10 @@ const openApiSpec = {
40519
40794
  schema: { $ref: '#/components/schemas/RestartBot' },
40520
40795
  example: {
40521
40796
  flowIds: ['greeting', 'support'],
40522
- port: 3009
40523
- }
40524
- }
40525
- }
40797
+ port: 3009,
40798
+ },
40799
+ },
40800
+ },
40526
40801
  },
40527
40802
  responses: {
40528
40803
  '200': {
@@ -40533,25 +40808,23 @@ const openApiSpec = {
40533
40808
  type: 'object',
40534
40809
  properties: {
40535
40810
  message: { type: 'string' },
40536
- tenantId: { type: 'string' }
40537
- }
40538
- }
40539
- }
40540
- }
40811
+ tenantId: { type: 'string' },
40812
+ },
40813
+ },
40814
+ },
40815
+ },
40541
40816
  },
40542
40817
  '400': { $ref: '#/components/responses/ValidationError' },
40543
- '404': { $ref: '#/components/responses/NotFound' }
40544
- }
40545
- }
40818
+ '404': { $ref: '#/components/responses/NotFound' },
40819
+ },
40820
+ },
40546
40821
  },
40547
40822
  '/bots/{tenantId}/stop': {
40548
40823
  post: {
40549
40824
  tags: ['Bots'],
40550
40825
  summary: 'Stop bot',
40551
40826
  description: 'Stop and remove the bot',
40552
- parameters: [
40553
- { $ref: '#/components/parameters/tenantId' }
40554
- ],
40827
+ parameters: [{ $ref: '#/components/parameters/tenantId' }],
40555
40828
  responses: {
40556
40829
  '200': {
40557
40830
  description: 'Bot stopped',
@@ -40561,16 +40834,16 @@ const openApiSpec = {
40561
40834
  type: 'object',
40562
40835
  properties: {
40563
40836
  success: { type: 'boolean' },
40564
- message: { type: 'string' }
40565
- }
40566
- }
40567
- }
40568
- }
40837
+ message: { type: 'string' },
40838
+ },
40839
+ },
40840
+ },
40841
+ },
40569
40842
  },
40570
- '404': { $ref: '#/components/responses/NotFound' }
40571
- }
40572
- }
40573
- }
40843
+ '404': { $ref: '#/components/responses/NotFound' },
40844
+ },
40845
+ },
40846
+ },
40574
40847
  },
40575
40848
  components: {
40576
40849
  parameters: {
@@ -40580,7 +40853,7 @@ const openApiSpec = {
40580
40853
  required: true,
40581
40854
  description: 'Bot tenant identifier',
40582
40855
  schema: { type: 'string' },
40583
- example: 'my-bot'
40856
+ example: 'my-bot',
40584
40857
  },
40585
40858
  flowId: {
40586
40859
  name: 'flowId',
@@ -40588,8 +40861,8 @@ const openApiSpec = {
40588
40861
  required: true,
40589
40862
  description: 'Flow identifier',
40590
40863
  schema: { type: 'string' },
40591
- example: 'greeting'
40592
- }
40864
+ example: 'greeting',
40865
+ },
40593
40866
  },
40594
40867
  schemas: {
40595
40868
  FlowStep: {
@@ -40599,8 +40872,8 @@ const openApiSpec = {
40599
40872
  answer: { type: 'string', description: 'Message to send', maxLength: 4096 },
40600
40873
  delay: { type: 'integer', description: 'Delay in ms before sending', minimum: 0, maximum: 30000 },
40601
40874
  media: { type: 'string', format: 'uri', description: 'Media URL to attach' },
40602
- capture: { type: 'boolean', description: 'Whether to capture user response' }
40603
- }
40875
+ capture: { type: 'boolean', description: 'Whether to capture user response' },
40876
+ },
40604
40877
  },
40605
40878
  CreateFlow: {
40606
40879
  type: 'object',
@@ -40609,34 +40882,28 @@ const openApiSpec = {
40609
40882
  id: { type: 'string', pattern: '^[a-zA-Z0-9-_]+$', maxLength: 50 },
40610
40883
  name: { type: 'string', maxLength: 100 },
40611
40884
  keyword: {
40612
- oneOf: [
40613
- { type: 'string' },
40614
- { type: 'array', items: { type: 'string' }, minItems: 1 }
40615
- ]
40885
+ oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }],
40616
40886
  },
40617
40887
  steps: {
40618
40888
  type: 'array',
40619
40889
  items: { $ref: '#/components/schemas/FlowStep' },
40620
- minItems: 1
40621
- }
40622
- }
40890
+ minItems: 1,
40891
+ },
40892
+ },
40623
40893
  },
40624
40894
  UpdateFlow: {
40625
40895
  type: 'object',
40626
40896
  properties: {
40627
40897
  name: { type: 'string', maxLength: 100 },
40628
40898
  keyword: {
40629
- oneOf: [
40630
- { type: 'string' },
40631
- { type: 'array', items: { type: 'string' }, minItems: 1 }
40632
- ]
40899
+ oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }],
40633
40900
  },
40634
40901
  steps: {
40635
40902
  type: 'array',
40636
40903
  items: { $ref: '#/components/schemas/FlowStep' },
40637
- minItems: 1
40638
- }
40639
- }
40904
+ minItems: 1,
40905
+ },
40906
+ },
40640
40907
  },
40641
40908
  FlowInfo: {
40642
40909
  type: 'object',
@@ -40644,8 +40911,8 @@ const openApiSpec = {
40644
40911
  id: { type: 'string' },
40645
40912
  name: { type: 'string' },
40646
40913
  dynamic: { type: 'boolean' },
40647
- config: { $ref: '#/components/schemas/CreateFlow' }
40648
- }
40914
+ config: { $ref: '#/components/schemas/CreateFlow' },
40915
+ },
40649
40916
  },
40650
40917
  FlowResponse: {
40651
40918
  type: 'object',
@@ -40654,8 +40921,8 @@ const openApiSpec = {
40654
40921
  id: { type: 'string' },
40655
40922
  name: { type: 'string' },
40656
40923
  dynamic: { type: 'boolean' },
40657
- config: { $ref: '#/components/schemas/CreateFlow' }
40658
- }
40924
+ config: { $ref: '#/components/schemas/CreateFlow' },
40925
+ },
40659
40926
  },
40660
40927
  CreateBot: {
40661
40928
  type: 'object',
@@ -40663,16 +40930,21 @@ const openApiSpec = {
40663
40930
  properties: {
40664
40931
  tenantId: { type: 'string', pattern: '^[a-zA-Z0-9-_]+$', maxLength: 50 },
40665
40932
  name: { type: 'string', maxLength: 100 },
40666
- flowIds: { type: 'array', items: { type: 'string' }, minItems: 1, description: 'At least one flowId is required' },
40933
+ flowIds: {
40934
+ type: 'array',
40935
+ items: { type: 'string' },
40936
+ minItems: 1,
40937
+ description: 'At least one flowId is required',
40938
+ },
40667
40939
  port: { type: 'integer', minimum: 1024, maximum: 65535 },
40668
- providerOptions: { type: 'object', additionalProperties: true }
40669
- }
40940
+ providerOptions: { type: 'object', additionalProperties: true },
40941
+ },
40670
40942
  },
40671
40943
  UpdateBot: {
40672
40944
  type: 'object',
40673
40945
  properties: {
40674
- name: { type: 'string', maxLength: 100 }
40675
- }
40946
+ name: { type: 'string', maxLength: 100 },
40947
+ },
40676
40948
  },
40677
40949
  RestartBot: {
40678
40950
  type: 'object',
@@ -40680,8 +40952,8 @@ const openApiSpec = {
40680
40952
  properties: {
40681
40953
  flowIds: { type: 'array', items: { type: 'string' }, minItems: 1 },
40682
40954
  port: { type: 'integer', minimum: 1024, maximum: 65535 },
40683
- name: { type: 'string', maxLength: 100 }
40684
- }
40955
+ name: { type: 'string', maxLength: 100 },
40956
+ },
40685
40957
  },
40686
40958
  BotInfo: {
40687
40959
  type: 'object',
@@ -40691,8 +40963,8 @@ const openApiSpec = {
40691
40963
  status: { type: 'string', enum: ['initializing', 'connected', 'disconnected', 'error'] },
40692
40964
  port: { type: 'integer' },
40693
40965
  createdAt: { type: 'string', format: 'date-time' },
40694
- uptime: { type: 'integer', description: 'Uptime in milliseconds' }
40695
- }
40966
+ uptime: { type: 'integer', description: 'Uptime in milliseconds' },
40967
+ },
40696
40968
  },
40697
40969
  BotResponse: {
40698
40970
  type: 'object',
@@ -40701,8 +40973,8 @@ const openApiSpec = {
40701
40973
  tenantId: { type: 'string' },
40702
40974
  name: { type: 'string' },
40703
40975
  status: { type: 'string' },
40704
- port: { type: 'integer' }
40705
- }
40976
+ port: { type: 'integer' },
40977
+ },
40706
40978
  },
40707
40979
  Error: {
40708
40980
  type: 'object',
@@ -40714,12 +40986,12 @@ const openApiSpec = {
40714
40986
  type: 'object',
40715
40987
  properties: {
40716
40988
  field: { type: 'string' },
40717
- message: { type: 'string' }
40718
- }
40719
- }
40720
- }
40721
- }
40722
- }
40989
+ message: { type: 'string' },
40990
+ },
40991
+ },
40992
+ },
40993
+ },
40994
+ },
40723
40995
  },
40724
40996
  responses: {
40725
40997
  NotFound: {
@@ -40727,9 +40999,9 @@ const openApiSpec = {
40727
40999
  content: {
40728
41000
  'application/json': {
40729
41001
  schema: { $ref: '#/components/schemas/Error' },
40730
- example: { error: 'Bot not found' }
40731
- }
40732
- }
41002
+ example: { error: 'Bot not found' },
41003
+ },
41004
+ },
40733
41005
  },
40734
41006
  ValidationError: {
40735
41007
  description: 'Validation error',
@@ -40738,57 +41010,52 @@ const openApiSpec = {
40738
41010
  schema: { $ref: '#/components/schemas/Error' },
40739
41011
  example: {
40740
41012
  error: 'Validation failed',
40741
- details: [
40742
- { field: 'tenantId', message: 'tenantId is required' }
40743
- ]
40744
- }
40745
- }
40746
- }
41013
+ details: [{ field: 'tenantId', message: 'tenantId is required' }],
41014
+ },
41015
+ },
41016
+ },
40747
41017
  },
40748
41018
  BadRequest: {
40749
41019
  description: 'Bad request',
40750
41020
  content: {
40751
41021
  'application/json': {
40752
41022
  schema: { $ref: '#/components/schemas/Error' },
40753
- example: { error: 'Cannot delete programmatic flows' }
40754
- }
40755
- }
41023
+ example: { error: 'Cannot delete programmatic flows' },
41024
+ },
41025
+ },
40756
41026
  },
40757
41027
  Conflict: {
40758
41028
  description: 'Resource already exists',
40759
41029
  content: {
40760
41030
  'application/json': {
40761
41031
  schema: { $ref: '#/components/schemas/Error' },
40762
- example: { error: 'Bot with this tenantId already exists' }
40763
- }
40764
- }
41032
+ example: { error: 'Bot with this tenantId already exists' },
41033
+ },
41034
+ },
40765
41035
  },
40766
41036
  Unauthorized: {
40767
41037
  description: 'Unauthorized',
40768
41038
  content: {
40769
41039
  'application/json': {
40770
41040
  schema: { $ref: '#/components/schemas/Error' },
40771
- example: { error: 'Unauthorized' }
40772
- }
40773
- }
40774
- }
41041
+ example: { error: 'Unauthorized' },
41042
+ },
41043
+ },
41044
+ },
40775
41045
  },
40776
41046
  securitySchemes: {
40777
41047
  ApiKeyAuth: {
40778
41048
  type: 'apiKey',
40779
41049
  in: 'header',
40780
- name: 'X-API-Key'
41050
+ name: 'X-API-Key',
40781
41051
  },
40782
41052
  BearerAuth: {
40783
41053
  type: 'http',
40784
- scheme: 'bearer'
40785
- }
40786
- }
41054
+ scheme: 'bearer',
41055
+ },
41056
+ },
40787
41057
  },
40788
- security: [
40789
- { ApiKeyAuth: [] },
40790
- { BearerAuth: [] }
40791
- ]
41058
+ security: [{ ApiKeyAuth: [] }, { BearerAuth: [] }],
40792
41059
  };
40793
41060
  /**
40794
41061
  * Generate Swagger UI HTML
@@ -40832,324 +41099,6 @@ function generateSwaggerHtml(specUrl) {
40832
41099
  </html>`;
40833
41100
  }
40834
41101
 
40835
- /**
40836
- * FlowRegistry manages flow definitions that can be used when creating bots
40837
- */
40838
- class FlowRegistry {
40839
- constructor() {
40840
- this.flows = new Map();
40841
- }
40842
- /**
40843
- * Register a programmatic flow (created with addKeyword)
40844
- */
40845
- register(id, name, flow) {
40846
- const definition = {
40847
- id,
40848
- name,
40849
- flow,
40850
- dynamic: false,
40851
- createdAt: new Date(),
40852
- updatedAt: new Date()
40853
- };
40854
- this.flows.set(id, definition);
40855
- return definition;
40856
- }
40857
- /**
40858
- * Register a dynamic flow from JSON configuration
40859
- */
40860
- registerDynamic(config) {
40861
- const { id, name, keyword, steps } = config;
40862
- const flow = this.buildFlowFromSteps(keyword, steps);
40863
- const definition = {
40864
- id,
40865
- name,
40866
- flow,
40867
- dynamic: true,
40868
- config,
40869
- createdAt: new Date(),
40870
- updatedAt: new Date()
40871
- };
40872
- this.flows.set(id, definition);
40873
- return definition;
40874
- }
40875
- /**
40876
- * Update a dynamic flow
40877
- */
40878
- update(id, updates) {
40879
- const existing = this.flows.get(id);
40880
- if (!existing || !existing.dynamic || !existing.config) {
40881
- return null;
40882
- }
40883
- // Merge updates with existing config
40884
- const newConfig = {
40885
- ...existing.config,
40886
- ...(updates.name && { name: updates.name }),
40887
- ...(updates.keyword && { keyword: updates.keyword }),
40888
- ...(updates.steps && { steps: updates.steps })
40889
- };
40890
- // Rebuild flow
40891
- const flow = this.buildFlowFromSteps(newConfig.keyword, newConfig.steps);
40892
- const definition = {
40893
- id,
40894
- name: newConfig.name,
40895
- flow,
40896
- dynamic: true,
40897
- config: newConfig,
40898
- createdAt: existing.createdAt,
40899
- updatedAt: new Date()
40900
- };
40901
- this.flows.set(id, definition);
40902
- return definition;
40903
- }
40904
- /**
40905
- * Remove a flow from registry
40906
- */
40907
- remove(id) {
40908
- return this.flows.delete(id);
40909
- }
40910
- /**
40911
- * Get a flow by ID
40912
- */
40913
- get(id) {
40914
- return this.flows.get(id);
40915
- }
40916
- /**
40917
- * Get all registered flows
40918
- */
40919
- getAll() {
40920
- return Array.from(this.flows.values());
40921
- }
40922
- /**
40923
- * Check if a flow exists
40924
- */
40925
- has(id) {
40926
- return this.flows.has(id);
40927
- }
40928
- /**
40929
- * Get all flow IDs
40930
- */
40931
- getIds() {
40932
- return Array.from(this.flows.keys());
40933
- }
40934
- /**
40935
- * Get count of registered flows
40936
- */
40937
- count() {
40938
- return this.flows.size;
40939
- }
40940
- /**
40941
- * Clear all flows
40942
- */
40943
- clear() {
40944
- this.flows.clear();
40945
- }
40946
- /**
40947
- * Get flows by type (dynamic or programmatic)
40948
- */
40949
- getByType(dynamic) {
40950
- return this.getAll().filter(f => f.dynamic === dynamic);
40951
- }
40952
- /**
40953
- * Resolve multiple flow IDs to Flow objects
40954
- */
40955
- resolveFlows(flowIds) {
40956
- const flows = [];
40957
- const missing = [];
40958
- for (const id of flowIds) {
40959
- const definition = this.flows.get(id);
40960
- if (definition) {
40961
- flows.push(definition.flow);
40962
- }
40963
- else {
40964
- missing.push(id);
40965
- }
40966
- }
40967
- return { flows, missing };
40968
- }
40969
- /**
40970
- * Build a flow from steps configuration
40971
- */
40972
- buildFlowFromSteps(keyword, steps) {
40973
- const keywords = Array.isArray(keyword)
40974
- ? keyword
40975
- : keyword;
40976
- let flow = addKeyword_1(keywords);
40977
- for (const step of steps) {
40978
- const options = {};
40979
- if (step.delay)
40980
- options.delay = step.delay;
40981
- if (step.media)
40982
- options.media = step.media;
40983
- if (step.capture)
40984
- options.capture = step.capture;
40985
- flow = flow.addAnswer(step.answer, Object.keys(options).length > 0 ? options : undefined);
40986
- }
40987
- return flow;
40988
- }
40989
- /**
40990
- * Export all dynamic flows as serializable configs
40991
- */
40992
- exportDynamicFlows() {
40993
- return this.getByType(true)
40994
- .filter(f => f.config)
40995
- .map(f => f.config);
40996
- }
40997
- /**
40998
- * Import dynamic flows from configs
40999
- */
41000
- importDynamicFlows(configs) {
41001
- const failed = [];
41002
- let imported = 0;
41003
- for (const config of configs) {
41004
- try {
41005
- if (!this.has(config.id)) {
41006
- this.registerDynamic(config);
41007
- imported++;
41008
- }
41009
- }
41010
- catch {
41011
- failed.push(config.id);
41012
- }
41013
- }
41014
- return { imported, failed };
41015
- }
41016
- }
41017
-
41018
- /**
41019
- * Simple in-memory rate limiter
41020
- */
41021
- class RateLimiter {
41022
- constructor(config = {}) {
41023
- this.store = new Map();
41024
- this.cleanupInterval = null;
41025
- this.config = {
41026
- maxRequests: config.maxRequests ?? 100,
41027
- windowMs: config.windowMs ?? 60000, // 1 minute
41028
- message: config.message ?? 'Too many requests, please try again later',
41029
- skipPaths: config.skipPaths ?? ['/docs', '/api/health'],
41030
- keyExtractor: config.keyExtractor ?? this.defaultKeyExtractor
41031
- };
41032
- // Cleanup expired entries every minute
41033
- this.cleanupInterval = setInterval(() => this.cleanup(), 60000);
41034
- }
41035
- /**
41036
- * Default key extractor - uses IP address
41037
- */
41038
- defaultKeyExtractor(req) {
41039
- const forwarded = req.headers['x-forwarded-for'];
41040
- if (forwarded) {
41041
- const ips = Array.isArray(forwarded) ? forwarded[0] : forwarded.split(',')[0];
41042
- return ips.trim();
41043
- }
41044
- return req.socket?.remoteAddress || 'unknown';
41045
- }
41046
- /**
41047
- * Check if request should be rate limited
41048
- */
41049
- isRateLimited(req) {
41050
- const url = req.url || '/';
41051
- // Skip certain paths
41052
- if (this.config.skipPaths.some(path => url.startsWith(path))) {
41053
- return { limited: false, remaining: this.config.maxRequests, resetTime: 0 };
41054
- }
41055
- const key = this.config.keyExtractor(req);
41056
- const now = Date.now();
41057
- let entry = this.store.get(key);
41058
- // If no entry or window expired, create new entry
41059
- if (!entry || now > entry.resetTime) {
41060
- entry = {
41061
- count: 1,
41062
- resetTime: now + this.config.windowMs
41063
- };
41064
- this.store.set(key, entry);
41065
- return {
41066
- limited: false,
41067
- remaining: this.config.maxRequests - 1,
41068
- resetTime: entry.resetTime
41069
- };
41070
- }
41071
- // Increment count
41072
- entry.count++;
41073
- // Check if over limit
41074
- if (entry.count > this.config.maxRequests) {
41075
- return {
41076
- limited: true,
41077
- remaining: 0,
41078
- resetTime: entry.resetTime
41079
- };
41080
- }
41081
- return {
41082
- limited: false,
41083
- remaining: this.config.maxRequests - entry.count,
41084
- resetTime: entry.resetTime
41085
- };
41086
- }
41087
- /**
41088
- * Create middleware function
41089
- */
41090
- middleware() {
41091
- return (req, res, next) => {
41092
- const result = this.isRateLimited(req);
41093
- // Set rate limit headers
41094
- res.setHeader('X-RateLimit-Limit', this.config.maxRequests.toString());
41095
- res.setHeader('X-RateLimit-Remaining', result.remaining.toString());
41096
- res.setHeader('X-RateLimit-Reset', result.resetTime.toString());
41097
- if (result.limited) {
41098
- res.setHeader('Retry-After', Math.ceil((result.resetTime - Date.now()) / 1000).toString());
41099
- res.writeHead(429, { 'Content-Type': 'application/json' });
41100
- res.end(JSON.stringify({
41101
- error: this.config.message,
41102
- retryAfter: Math.ceil((result.resetTime - Date.now()) / 1000)
41103
- }));
41104
- return;
41105
- }
41106
- next();
41107
- };
41108
- }
41109
- /**
41110
- * Reset rate limit for a specific key
41111
- */
41112
- reset(key) {
41113
- this.store.delete(key);
41114
- }
41115
- /**
41116
- * Clear all rate limit data
41117
- */
41118
- clear() {
41119
- this.store.clear();
41120
- }
41121
- /**
41122
- * Cleanup expired entries
41123
- */
41124
- cleanup() {
41125
- const now = Date.now();
41126
- for (const [key, entry] of this.store.entries()) {
41127
- if (now > entry.resetTime) {
41128
- this.store.delete(key);
41129
- }
41130
- }
41131
- }
41132
- /**
41133
- * Stop the cleanup interval
41134
- */
41135
- destroy() {
41136
- if (this.cleanupInterval) {
41137
- clearInterval(this.cleanupInterval);
41138
- this.cleanupInterval = null;
41139
- }
41140
- this.store.clear();
41141
- }
41142
- /**
41143
- * Get current stats
41144
- */
41145
- getStats() {
41146
- return {
41147
- activeKeys: this.store.size,
41148
- config: this.config
41149
- };
41150
- }
41151
- }
41152
-
41153
41102
  /**
41154
41103
  * REST API for managing bots via HTTP endpoints using Polka
41155
41104
  */
@@ -41271,15 +41220,12 @@ class BotManagerApi {
41271
41220
  * Start the API server
41272
41221
  */
41273
41222
  start() {
41274
- this.app = polka()
41275
- .use(this.cors());
41223
+ this.app = polka().use(this.cors());
41276
41224
  // Add rate limiter if enabled
41277
41225
  if (this.rateLimiter) {
41278
41226
  this.app.use(this.rateLimiter.middleware());
41279
41227
  }
41280
- this.app
41281
- .use(this.jsonParser())
41282
- .use(this.auth());
41228
+ this.app.use(this.jsonParser()).use(this.auth());
41283
41229
  // Swagger UI - no auth required
41284
41230
  this.app.get('/docs', (req, res) => {
41285
41231
  res.writeHead(200, { 'Content-Type': 'text/html' });
@@ -41296,23 +41242,23 @@ class BotManagerApi {
41296
41242
  ...health,
41297
41243
  timestamp: new Date().toISOString(),
41298
41244
  flows: this.flowRegistry.count(),
41299
- rateLimiter: this.rateLimiter?.getStats() || null
41245
+ rateLimiter: this.rateLimiter?.getStats() || null,
41300
41246
  });
41301
41247
  });
41302
41248
  // ============ FLOWS ============
41303
41249
  // Get all flows
41304
41250
  this.app.get('/api/flows', (req, res) => {
41305
- const flows = this.flowRegistry.getAll().map(f => ({
41251
+ const flows = this.flowRegistry.getAll().map((f) => ({
41306
41252
  id: f.id,
41307
41253
  name: f.name,
41308
41254
  dynamic: f.dynamic,
41309
41255
  config: f.dynamic ? f.config : undefined,
41310
41256
  createdAt: f.createdAt,
41311
- updatedAt: f.updatedAt
41257
+ updatedAt: f.updatedAt,
41312
41258
  }));
41313
41259
  this.sendJson(res, 200, {
41314
41260
  count: flows.length,
41315
- flows
41261
+ flows,
41316
41262
  });
41317
41263
  });
41318
41264
  // Create a new dynamic flow
@@ -41332,7 +41278,7 @@ class BotManagerApi {
41332
41278
  dynamic: flow.dynamic,
41333
41279
  config: flow.dynamic ? flow.config : undefined,
41334
41280
  createdAt: flow.createdAt,
41335
- updatedAt: flow.updatedAt
41281
+ updatedAt: flow.updatedAt,
41336
41282
  });
41337
41283
  });
41338
41284
  // Update a dynamic flow
@@ -41348,7 +41294,7 @@ class BotManagerApi {
41348
41294
  }
41349
41295
  if (!flow.dynamic) {
41350
41296
  return this.sendJson(res, 400, {
41351
- error: 'Cannot delete programmatic flows. Only dynamic flows can be deleted.'
41297
+ error: 'Cannot delete programmatic flows. Only dynamic flows can be deleted.',
41352
41298
  });
41353
41299
  }
41354
41300
  this.flowRegistry.remove(flowId);
@@ -41359,7 +41305,7 @@ class BotManagerApi {
41359
41305
  this.app.get('/api/bots', (req, res) => {
41360
41306
  this.sendJson(res, 200, {
41361
41307
  count: this.manager.getBotCount(),
41362
- bots: this.manager.getBotsInfo()
41308
+ bots: this.manager.getBotsInfo(),
41363
41309
  });
41364
41310
  });
41365
41311
  // Create bot
@@ -41382,7 +41328,7 @@ class BotManagerApi {
41382
41328
  uptime: Date.now() - bot.createdAt.getTime(),
41383
41329
  providerType: bot.providerType,
41384
41330
  databaseType: bot.databaseType,
41385
- reconnectState: this.manager.getReconnectState(tenantId)
41331
+ reconnectState: this.manager.getReconnectState(tenantId),
41386
41332
  });
41387
41333
  });
41388
41334
  // Update bot
@@ -41413,7 +41359,7 @@ class BotManagerApi {
41413
41359
  this.sendJson(res, 200, {
41414
41360
  status: bot.status,
41415
41361
  qr: qr || null,
41416
- message: qr ? 'Scan QR to connect' : 'QR not available yet'
41362
+ message: qr ? 'Scan QR to connect' : 'QR not available yet',
41417
41363
  });
41418
41364
  });
41419
41365
  // Restart bot
@@ -41426,7 +41372,7 @@ class BotManagerApi {
41426
41372
  const success = await this.manager.reconnectBot(tenantId);
41427
41373
  this.sendJson(res, success ? 200 : 404, {
41428
41374
  success,
41429
- message: success ? 'Bot reconnection initiated' : 'Bot not found or no stored config'
41375
+ message: success ? 'Bot reconnection initiated' : 'Bot not found or no stored config',
41430
41376
  });
41431
41377
  });
41432
41378
  // Stop bot
@@ -41435,7 +41381,7 @@ class BotManagerApi {
41435
41381
  const removed = await this.manager.removeBot(tenantId);
41436
41382
  this.sendJson(res, removed ? 200 : 404, {
41437
41383
  success: removed,
41438
- message: removed ? 'Bot stopped' : 'Bot not found'
41384
+ message: removed ? 'Bot stopped' : 'Bot not found',
41439
41385
  });
41440
41386
  });
41441
41387
  // Start server
@@ -41465,7 +41411,7 @@ class BotManagerApi {
41465
41411
  if (!validation.success || !validation.data) {
41466
41412
  return this.sendJson(res, 400, {
41467
41413
  error: 'Validation failed',
41468
- details: validation.errors
41414
+ details: validation.errors,
41469
41415
  });
41470
41416
  }
41471
41417
  const { tenantId, name, flowIds, port, providerOptions } = validation.data;
@@ -41474,7 +41420,7 @@ class BotManagerApi {
41474
41420
  if (missing.length > 0) {
41475
41421
  return this.sendJson(res, 400, {
41476
41422
  error: `Flows not found: ${missing.join(', ')}`,
41477
- availableFlows: this.flowRegistry.getIds()
41423
+ availableFlows: this.flowRegistry.getIds(),
41478
41424
  });
41479
41425
  }
41480
41426
  if (this.manager.hasBot(tenantId)) {
@@ -41486,7 +41432,7 @@ class BotManagerApi {
41486
41432
  name,
41487
41433
  flows,
41488
41434
  port,
41489
- providerOptions
41435
+ providerOptions,
41490
41436
  });
41491
41437
  this.sendJson(res, 201, {
41492
41438
  message: 'Bot created successfully',
@@ -41496,7 +41442,7 @@ class BotManagerApi {
41496
41442
  port: bot.port,
41497
41443
  flowsUsed: flowIds,
41498
41444
  providerType: bot.providerType,
41499
- databaseType: bot.databaseType
41445
+ databaseType: bot.databaseType,
41500
41446
  });
41501
41447
  }
41502
41448
  catch (error) {
@@ -41509,7 +41455,7 @@ class BotManagerApi {
41509
41455
  if (!validation.success) {
41510
41456
  return this.sendJson(res, 400, {
41511
41457
  error: 'Validation failed',
41512
- details: validation.errors
41458
+ details: validation.errors,
41513
41459
  });
41514
41460
  }
41515
41461
  const bot = this.manager.getBot(tenantId);
@@ -41522,7 +41468,7 @@ class BotManagerApi {
41522
41468
  this.sendJson(res, 200, {
41523
41469
  message: 'Bot updated',
41524
41470
  tenantId: bot.tenantId,
41525
- name: bot.name
41471
+ name: bot.name,
41526
41472
  });
41527
41473
  }
41528
41474
  async handleRestartBot(req, res) {
@@ -41531,16 +41477,14 @@ class BotManagerApi {
41531
41477
  if (!validation.success || !validation.data) {
41532
41478
  return this.sendJson(res, 400, {
41533
41479
  error: 'Validation failed',
41534
- details: validation.errors
41480
+ details: validation.errors,
41535
41481
  });
41536
41482
  }
41537
41483
  const { flowIds, port, name } = validation.data;
41538
41484
  const { flows, missing } = this.flowRegistry.resolveFlows(flowIds);
41539
41485
  if (flows.length === 0) {
41540
41486
  return this.sendJson(res, 400, {
41541
- error: missing.length > 0
41542
- ? `Flows not found: ${missing.join(', ')}`
41543
- : 'No valid flows found in registry'
41487
+ error: missing.length > 0 ? `Flows not found: ${missing.join(', ')}` : 'No valid flows found in registry',
41544
41488
  });
41545
41489
  }
41546
41490
  try {
@@ -41551,7 +41495,7 @@ class BotManagerApi {
41551
41495
  this.sendJson(res, 200, {
41552
41496
  message: 'Bot restarted',
41553
41497
  tenantId,
41554
- status: newBot.status
41498
+ status: newBot.status,
41555
41499
  });
41556
41500
  }
41557
41501
  catch (error) {
@@ -41564,7 +41508,7 @@ class BotManagerApi {
41564
41508
  if (!validation.success || !validation.data) {
41565
41509
  return this.sendJson(res, 400, {
41566
41510
  error: 'Validation failed',
41567
- details: validation.errors
41511
+ details: validation.errors,
41568
41512
  });
41569
41513
  }
41570
41514
  const { id } = validation.data;
@@ -41579,7 +41523,7 @@ class BotManagerApi {
41579
41523
  name: flowDef.name,
41580
41524
  dynamic: true,
41581
41525
  config: flowDef.config,
41582
- createdAt: flowDef.createdAt
41526
+ createdAt: flowDef.createdAt,
41583
41527
  });
41584
41528
  }
41585
41529
  catch (error) {
@@ -41594,14 +41538,14 @@ class BotManagerApi {
41594
41538
  }
41595
41539
  if (!existing.dynamic) {
41596
41540
  return this.sendJson(res, 400, {
41597
- error: 'Cannot update programmatic flows. Only dynamic flows can be updated.'
41541
+ error: 'Cannot update programmatic flows. Only dynamic flows can be updated.',
41598
41542
  });
41599
41543
  }
41600
41544
  const validation = validate(updateFlowSchema, req.body);
41601
41545
  if (!validation.success) {
41602
41546
  return this.sendJson(res, 400, {
41603
41547
  error: 'Validation failed',
41604
- details: validation.errors
41548
+ details: validation.errors,
41605
41549
  });
41606
41550
  }
41607
41551
  try {
@@ -41615,7 +41559,7 @@ class BotManagerApi {
41615
41559
  name: flowDef.name,
41616
41560
  dynamic: true,
41617
41561
  config: flowDef.config,
41618
- updatedAt: flowDef.updatedAt
41562
+ updatedAt: flowDef.updatedAt,
41619
41563
  });
41620
41564
  }
41621
41565
  catch (error) {
@@ -41633,7 +41577,7 @@ class PersistenceManager {
41633
41577
  this.config = {
41634
41578
  persistenceDir: config.persistenceDir ?? './data',
41635
41579
  fileName: config.fileName ?? 'bots.json',
41636
- autoSave: config.autoSave ?? true
41580
+ autoSave: config.autoSave ?? true,
41637
41581
  };
41638
41582
  this.filePath = require$$1$5.join(this.config.persistenceDir, this.config.fileName);
41639
41583
  this.load();
@@ -41651,7 +41595,7 @@ class PersistenceManager {
41651
41595
  databaseOptions: config.databaseOptions,
41652
41596
  providerClassName,
41653
41597
  databaseClassName,
41654
- createdAt: new Date().toISOString()
41598
+ createdAt: new Date().toISOString(),
41655
41599
  };
41656
41600
  this.data.set(tenantId, serializableConfig);
41657
41601
  if (this.config.autoSave) {
@@ -41713,7 +41657,7 @@ class PersistenceManager {
41713
41657
  const dataToSave = {
41714
41658
  version: 1,
41715
41659
  updatedAt: new Date().toISOString(),
41716
- bots: Array.from(this.data.entries())
41660
+ bots: Array.from(this.data.entries()),
41717
41661
  };
41718
41662
  require$$0$9.writeFileSync(this.filePath, JSON.stringify(dataToSave, null, 2), 'utf-8');
41719
41663
  }