mojulo 0.0.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/README.md +54 -4
  2. package/lib/audit-logger-new.js +11 -0
  3. package/lib/auth/gate.js +25 -0
  4. package/lib/auth/service.js +17 -0
  5. package/lib/auth/session.js +63 -0
  6. package/lib/builder/chat-processor.js +607 -0
  7. package/lib/builder/composer-bridge.js +82 -0
  8. package/lib/builder/evaluator.js +159 -0
  9. package/lib/builder/executor.js +252 -0
  10. package/lib/builder/index.js +48 -0
  11. package/lib/builder/session.js +248 -0
  12. package/lib/builder/system-prompt.js +422 -0
  13. package/lib/builder/tone-presets.js +75 -0
  14. package/lib/builder/tool-executors.js +1527 -0
  15. package/lib/builder/tools.js +338 -0
  16. package/lib/builder/validators.js +239 -0
  17. package/lib/composer/composer.js +225 -0
  18. package/lib/composer/index.js +40 -0
  19. package/lib/composer/protocols/00_base.txt +19 -0
  20. package/lib/composer/protocols/01_knowledge.txt +9 -0
  21. package/lib/composer/protocols/02_form-gathering.txt +32 -0
  22. package/lib/composer/protocols/03_appointments.txt +16 -0
  23. package/lib/composer/protocols/04_triage.txt +15 -0
  24. package/lib/composer/protocols/05_optical-read.txt +22 -0
  25. package/lib/composer/response-builder.js +98 -0
  26. package/lib/config-builder.js +650 -0
  27. package/lib/db/ids.js +10 -0
  28. package/lib/db/index.js +179 -0
  29. package/lib/db/repositories/apiKeys.js +72 -0
  30. package/lib/db/repositories/auditLogs.js +12 -0
  31. package/lib/db/repositories/botSpaces.js +12 -0
  32. package/lib/db/repositories/builderSessions.js +312 -0
  33. package/lib/db/repositories/deploymentEvents.js +12 -0
  34. package/lib/db/repositories/deployments.js +385 -0
  35. package/lib/db/repositories/documents.js +68 -0
  36. package/lib/db/repositories/mcpJobs.js +84 -0
  37. package/lib/deployers/bot-fleet.js +110 -0
  38. package/lib/deployers/bot-proxy.js +72 -0
  39. package/lib/deployers/build.js +89 -0
  40. package/lib/deployers/cloud-deploy.js +310 -0
  41. package/lib/deployers/docker.js +439 -0
  42. package/lib/deployers/fly.js +432 -0
  43. package/lib/deployers/index.js +38 -0
  44. package/lib/deployment-auth.js +36 -0
  45. package/lib/document-parser.js +171 -0
  46. package/lib/embedder/chunker.js +93 -0
  47. package/lib/embedder/local.js +101 -0
  48. package/lib/embedder/preview-rag.js +93 -0
  49. package/lib/envelope-schema.js +54 -0
  50. package/lib/fleet/scoped-sql.js +342 -0
  51. package/lib/form-schema-config/base.js +135 -0
  52. package/lib/form-schema-config/index.js +286 -0
  53. package/lib/form-schema-config/locales/af-ZA.js +153 -0
  54. package/lib/form-schema-config/locales/ar-AE.js +142 -0
  55. package/lib/form-schema-config/locales/ar-SA.js +164 -0
  56. package/lib/form-schema-config/locales/de-DE.js +152 -0
  57. package/lib/form-schema-config/locales/en-AU.js +161 -0
  58. package/lib/form-schema-config/locales/en-CA.js +115 -0
  59. package/lib/form-schema-config/locales/en-GB.js +132 -0
  60. package/lib/form-schema-config/locales/en-IN.js +219 -0
  61. package/lib/form-schema-config/locales/en-MY.js +171 -0
  62. package/lib/form-schema-config/locales/en-NG.js +198 -0
  63. package/lib/form-schema-config/locales/en-PH.js +186 -0
  64. package/lib/form-schema-config/locales/en-SG.js +153 -0
  65. package/lib/form-schema-config/locales/en-US.js +138 -0
  66. package/lib/form-schema-config/locales/es-ES.js +171 -0
  67. package/lib/form-schema-config/locales/es-MX.js +193 -0
  68. package/lib/form-schema-config/locales/fr-CA.js +138 -0
  69. package/lib/form-schema-config/locales/fr-FR.js +155 -0
  70. package/lib/form-schema-config/locales/hi-IN.js +219 -0
  71. package/lib/form-schema-config/locales/it-IT.js +157 -0
  72. package/lib/form-schema-config/locales/ja-JP.js +169 -0
  73. package/lib/form-schema-config/locales/ko-KR.js +140 -0
  74. package/lib/form-schema-config/locales/nl-NL.js +149 -0
  75. package/lib/form-schema-config/locales/pt-BR.js +168 -0
  76. package/lib/form-schema-config/locales/zh-CN.js +172 -0
  77. package/lib/form-schema-config/locales/zh-HK.js +142 -0
  78. package/lib/form-structure-schema.js +191 -0
  79. package/lib/llm-providers.js +828 -0
  80. package/lib/markdown.js +197 -0
  81. package/lib/mcp/catalysts/appointment-to-calendar.md +84 -0
  82. package/lib/mcp/catalysts/conversations-to-channel-digest.md +104 -0
  83. package/lib/mcp/catalysts/document-extract-to-store.md +92 -0
  84. package/lib/mcp/catalysts/knowledge-gap-miner.md +96 -0
  85. package/lib/mcp/catalysts/loader.js +144 -0
  86. package/lib/mcp/catalysts/qualify-lead-to-crm.md +83 -0
  87. package/lib/mcp/catalysts/scan-conversations-for-signal.md +92 -0
  88. package/lib/mcp/catalysts/submission-to-ticket.md +83 -0
  89. package/lib/mcp/catalysts/submissions-to-warehouse.md +103 -0
  90. package/lib/mcp/catalysts/weekly-submissions-digest.md +82 -0
  91. package/lib/mcp/jobs.js +64 -0
  92. package/lib/mcp/server.js +184 -0
  93. package/lib/mcp/session-binding.js +130 -0
  94. package/lib/mcp/tools/build.js +123 -0
  95. package/lib/mcp/tools/catalysts.js +477 -0
  96. package/lib/mcp/tools/context.js +325 -0
  97. package/lib/mcp/tools/fleet.js +391 -0
  98. package/lib/mcp/tools/jobs-tools.js +240 -0
  99. package/lib/mcp/tools/operate.js +314 -0
  100. package/lib/preview/build-preview-config.js +136 -0
  101. package/lib/rate-limiter.js +11 -0
  102. package/lib/resolve-api-key.js +142 -0
  103. package/lib/storage/index.js +40 -0
  104. package/messages/de.json +2136 -0
  105. package/messages/en.json +2136 -0
  106. package/messages/es.json +2136 -0
  107. package/messages/fr.json +2136 -0
  108. package/messages/it.json +2136 -0
  109. package/messages/ja.json +2136 -0
  110. package/messages/ko.json +2136 -0
  111. package/messages/nl.json +2136 -0
  112. package/messages/pl.json +2136 -0
  113. package/messages/pt.json +2136 -0
  114. package/messages/ru.json +2136 -0
  115. package/messages/uk.json +2136 -0
  116. package/messages/zh.json +2136 -0
  117. package/package.json +68 -5
  118. package/scripts/mcp-config.mjs +162 -0
  119. package/scripts/mcp-stdio-loader.mjs +42 -0
  120. package/scripts/mcp-stdio.mjs +108 -0
  121. package/scripts/mojulo-paths.mjs +48 -0
@@ -0,0 +1,338 @@
1
+ /**
2
+ * Builder Tools for Inverted Flow
3
+ *
4
+ * Tool definitions for the "Claude proposes, User disposes" architecture.
5
+ * These tools allow Claude to:
6
+ * - Process documents and generate RAG summaries
7
+ * - Infer user intent from context
8
+ * - Recommend appropriate protocols
9
+ * - Generate configurations for each protocol
10
+ * - Compose bot identity
11
+ * - Deploy the final bot
12
+ */
13
+
14
+ /**
15
+ * Tool definitions for Claude's inverted builder flow
16
+ */
17
+ export const BUILDER_TOOLS = [
18
+ {
19
+ name: 'process_documents',
20
+ description: 'Parse uploaded documents and embed them locally via the bundled multilingual-e5-small ONNX model. Call this first when documents are attached.',
21
+ input_schema: {
22
+ type: 'object',
23
+ properties: {
24
+ documentIds: {
25
+ type: 'array',
26
+ items: { type: 'string' },
27
+ description: 'Array of document IDs to process',
28
+ },
29
+ },
30
+ required: ['documentIds'],
31
+ },
32
+ },
33
+ {
34
+ name: 'infer_intent',
35
+ description: 'Analyze the user message and document digest to determine the bot type and required capabilities. Returns intent classification with confidence score.',
36
+ input_schema: {
37
+ type: 'object',
38
+ properties: {
39
+ userMessage: {
40
+ type: 'string',
41
+ description: 'The user\'s original message describing what they want',
42
+ },
43
+ domainDigest: {
44
+ type: 'string',
45
+ description: 'Build-time digest of processed documents (if any) — produced by process_documents',
46
+ },
47
+ },
48
+ required: ['userMessage'],
49
+ },
50
+ },
51
+ {
52
+ name: 'recommend_protocols',
53
+ description: 'Based on the inferred intent and context, recommend which protocols (Knowledge, Forms, Appointments, Triage) should be enabled. Provides reasoning for each recommendation.',
54
+ input_schema: {
55
+ type: 'object',
56
+ properties: {
57
+ intent: {
58
+ type: 'string',
59
+ description: 'The inferred intent (e.g., support_bot, lead_gen, appointment_scheduler)',
60
+ },
61
+ domainDigest: {
62
+ type: 'string',
63
+ description: 'Build-time digest of processed documents — produced by process_documents',
64
+ },
65
+ userMessage: {
66
+ type: 'string',
67
+ description: 'Original user message for additional context',
68
+ },
69
+ },
70
+ required: ['intent'],
71
+ },
72
+ },
73
+ {
74
+ name: 'generate_form_schema',
75
+ description: 'Generate a form schema for data collection based on the bot\'s purpose. Creates field definitions for ghost forms.',
76
+ input_schema: {
77
+ type: 'object',
78
+ properties: {
79
+ description: {
80
+ type: 'string',
81
+ description: 'Description of what data the form should collect',
82
+ },
83
+ formType: {
84
+ type: 'string',
85
+ enum: ['lead_capture', 'support_ticket', 'feedback', 'booking_info', 'custom'],
86
+ description: 'Type of form to generate',
87
+ },
88
+ locale: {
89
+ type: 'string',
90
+ description: 'Locale for field labels (e.g., en, es, fr)',
91
+ default: 'en',
92
+ },
93
+ afterSubmitChatMessage: {
94
+ type: 'string',
95
+ description: 'Message shown to users after they submit the form. If not provided, a contextual message will be generated based on the form purpose.',
96
+ },
97
+ },
98
+ required: ['description'],
99
+ },
100
+ },
101
+ {
102
+ name: 'generate_appointment_config',
103
+ description: 'Generate appointment/booking configuration for calendar integrations.',
104
+ input_schema: {
105
+ type: 'object',
106
+ properties: {
107
+ domainDigest: {
108
+ type: 'string',
109
+ description: 'Build-time digest of documents — used to extract appointment types/services',
110
+ },
111
+ businessType: {
112
+ type: 'string',
113
+ description: 'Type of business (e.g., healthcare, salon, consulting)',
114
+ },
115
+ calendarProviders: {
116
+ type: 'array',
117
+ items: { type: 'string' },
118
+ description: 'Available calendar providers (google, outlook, etc.)',
119
+ },
120
+ },
121
+ },
122
+ },
123
+ {
124
+ name: 'generate_triage_config',
125
+ description: 'Generate triage routing configuration for multi-bot orchestration. Defines destinations where users can be routed based on their intent. Each route has a name, description (for RAG matching), and target URL.',
126
+ input_schema: {
127
+ type: 'object',
128
+ properties: {
129
+ routes: {
130
+ type: 'array',
131
+ items: {
132
+ type: 'object',
133
+ properties: {
134
+ name: {
135
+ type: 'string',
136
+ description: 'Display name for the route (e.g., "Sales Support", "Technical Help")',
137
+ },
138
+ description: {
139
+ type: 'string',
140
+ description: 'Description of when users should be routed here - used for RAG matching (e.g., "Users asking about pricing, quotes, or purchasing")',
141
+ },
142
+ url: {
143
+ type: 'string',
144
+ description: 'Target URL to route users to',
145
+ },
146
+ deploymentId: {
147
+ type: 'string',
148
+ description: 'Optional deployment ID if routing to another bot. If not provided, will be generated from the name.',
149
+ },
150
+ },
151
+ required: ['name', 'description', 'url'],
152
+ },
153
+ description: 'Array of routing destinations',
154
+ },
155
+ domainDigest: {
156
+ type: 'string',
157
+ description: 'Build-time digest of documents — used to infer appropriate routes',
158
+ },
159
+ userMessage: {
160
+ type: 'string',
161
+ description: 'Original user message for context about routing needs',
162
+ },
163
+ },
164
+ required: ['routes'],
165
+ },
166
+ },
167
+ {
168
+ name: 'generate_optical_read_config',
169
+ description:
170
+ 'Generate Optical Read extraction fields for the bot. The user names slots they want pulled out of an uploaded image; the model resolves them against its own visual prior. The optional hint primes location ("bottom-right of card frame") or format ("MM/DD/YYYY") and is the load-bearing tuning primitive.',
171
+ input_schema: {
172
+ type: 'object',
173
+ properties: {
174
+ fields: {
175
+ type: 'array',
176
+ items: {
177
+ type: 'object',
178
+ properties: {
179
+ label: {
180
+ type: 'string',
181
+ description: 'Display label for the field (e.g., "Date of Birth")',
182
+ },
183
+ idName: {
184
+ type: 'string',
185
+ description:
186
+ 'snake_case key used in extractedFields. If omitted, slugified from label.',
187
+ },
188
+ hint: {
189
+ type: 'string',
190
+ description:
191
+ 'Optional priming hint — location or format. Examples: "MM/DD/YYYY", "bottom-right of card frame", "all caps".',
192
+ },
193
+ },
194
+ required: ['label'],
195
+ },
196
+ description: 'Array of extraction fields',
197
+ },
198
+ },
199
+ required: ['fields'],
200
+ },
201
+ },
202
+ {
203
+ name: 'compose_identity',
204
+ description: 'Generate the bot\'s identity including name, objective, first message, and suggested prompts. Pass userMessage and domainDigest for contextual LLM-generated identity.',
205
+ input_schema: {
206
+ type: 'object',
207
+ properties: {
208
+ intent: {
209
+ type: 'string',
210
+ description: 'The inferred bot intent/type',
211
+ },
212
+ domainDigest: {
213
+ type: 'string',
214
+ description: 'Build-time document digest — used to generate contextual firstMessage and objective',
215
+ },
216
+ userMessage: {
217
+ type: 'string',
218
+ description: 'The original user message describing what they want - used with domainDigest to generate contextual identity',
219
+ },
220
+ organizationName: {
221
+ type: 'string',
222
+ description: 'Name of the organization deploying the bot',
223
+ },
224
+ enabledProtocols: {
225
+ type: 'object',
226
+ description: 'Which protocols are enabled',
227
+ },
228
+ },
229
+ required: ['intent'],
230
+ },
231
+ },
232
+ {
233
+ name: 'set_suggested_prompts',
234
+ description: 'Set the suggested prompts for the bot. Call this AFTER compose_identity to provide localized prompts in the same language as the documents/user. Generate 3 short, specific prompts based on the document content.',
235
+ input_schema: {
236
+ type: 'object',
237
+ properties: {
238
+ prompts: {
239
+ type: 'array',
240
+ items: { type: 'string' },
241
+ minItems: 1,
242
+ maxItems: 5,
243
+ description: 'Array of 3 suggested prompts in the SAME LANGUAGE as the documents. Each prompt should be short (max 8 words), specific to the content, and start with an action word.',
244
+ },
245
+ },
246
+ required: ['prompts'],
247
+ },
248
+ },
249
+ {
250
+ name: 'generate_bot_summary',
251
+ description: 'Generate a summarized description of the bot, will be used as meta-data. Call this after all configurations are complete, before deployment. The summary describes what the bot does, what knowledge it has, and its capabilities.',
252
+ input_schema: {
253
+ type: 'object',
254
+ properties: {},
255
+ },
256
+ },
257
+ {
258
+ name: 'save_modular_bot',
259
+ description:
260
+ "Save the bot's composed configuration to a deployment row in SQLite. Only call after user confirms the recommended protocols. This does NOT build the artifact — the user clicks 'Build & Download' afterward.",
261
+ input_schema: {
262
+ type: 'object',
263
+ properties: {
264
+ sessionId: {
265
+ type: 'string',
266
+ description: 'The modular session ID',
267
+ },
268
+ confirmedProtocols: {
269
+ type: 'object',
270
+ description: 'User-confirmed protocol selections',
271
+ },
272
+ },
273
+ required: ['sessionId', 'confirmedProtocols'],
274
+ },
275
+ },
276
+ ];
277
+
278
+ /**
279
+ * Tool name to display label mapping
280
+ */
281
+ export const TOOL_LABELS = {
282
+ process_documents: 'Processing documents',
283
+ infer_intent: 'Analyzing intent',
284
+ recommend_protocols: 'Recommending protocols',
285
+ generate_form_schema: 'Generating form',
286
+ generate_appointment_config: 'Configuring appointments',
287
+ generate_triage_config: 'Configuring triage routes',
288
+ generate_optical_read_config: 'Configuring optical read fields',
289
+ compose_identity: 'Composing identity',
290
+ set_suggested_prompts: 'Setting suggested prompts',
291
+ generate_bot_summary: 'Generating bot summary',
292
+ save_modular_bot: 'Saving bot configuration',
293
+ };
294
+
295
+ /**
296
+ * Tool name to icon mapping (for UI)
297
+ */
298
+ export const TOOL_ICONS = {
299
+ process_documents: 'document',
300
+ infer_intent: 'target',
301
+ recommend_protocols: 'puzzle',
302
+ generate_form_schema: 'form',
303
+ generate_appointment_config: 'calendar',
304
+ generate_triage_config: 'split',
305
+ generate_optical_read_config: 'eye',
306
+ compose_identity: 'robot',
307
+ set_suggested_prompts: 'message-square',
308
+ generate_bot_summary: 'sparkles',
309
+ save_modular_bot: 'save',
310
+ };
311
+
312
+ /**
313
+ * Get tool definition by name
314
+ */
315
+ export function getToolByName(name) {
316
+ return BUILDER_TOOLS.find(tool => tool.name === name);
317
+ }
318
+
319
+ /**
320
+ * Validate tool input against schema
321
+ */
322
+ export function validateToolInput(toolName, input) {
323
+ const tool = getToolByName(toolName);
324
+ if (!tool) {
325
+ return { valid: false, error: `Unknown tool: ${toolName}` };
326
+ }
327
+
328
+ const schema = tool.input_schema;
329
+ const required = schema.required || [];
330
+
331
+ for (const field of required) {
332
+ if (input[field] === undefined || input[field] === null) {
333
+ return { valid: false, error: `Missing required field: ${field}` };
334
+ }
335
+ }
336
+
337
+ return { valid: true };
338
+ }
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Protocol Validators for Builder Flow
3
+ *
4
+ * Validates protocol-specific configuration before deployment.
5
+ */
6
+
7
+ /**
8
+ * Protocol-specific validators
9
+ */
10
+ export const PROTOCOL_VALIDATORS = {
11
+ /**
12
+ * Validate knowledge protocol configuration
13
+ * @param {Object} data - Knowledge protocol data
14
+ * @param {Object} options - Validation options
15
+ * @returns {{ valid: boolean, error?: string }}
16
+ */
17
+ knowledge: (data, options = {}) => {
18
+ if (options.skipRag) {
19
+ return { valid: true };
20
+ }
21
+
22
+ // All builds are vector-only — process_documents embeds locally and
23
+ // stashes the embedding blob on the session. Documents are the only
24
+ // valid source; a hand-typed summary alone can no longer satisfy the
25
+ // step because there's nothing to embed.
26
+ const hasDocuments = data.documents && data.documents.length > 0;
27
+ if (!hasDocuments) {
28
+ return { valid: false, error: 'At least one document is required' };
29
+ }
30
+
31
+ return { valid: true };
32
+ },
33
+
34
+ /**
35
+ * Validate form gathering protocol configuration
36
+ * @param {Object} data - Form gathering protocol data
37
+ * @returns {{ valid: boolean, error?: string }}
38
+ */
39
+ formGathering: (data) => {
40
+ if (!data.generatedFormJson) {
41
+ return { valid: false, error: 'Form structure is required' };
42
+ }
43
+
44
+ try {
45
+ const parsed =
46
+ typeof data.generatedFormJson === 'string'
47
+ ? JSON.parse(data.generatedFormJson)
48
+ : data.generatedFormJson;
49
+
50
+ if (!parsed.sections || !Array.isArray(parsed.sections)) {
51
+ return { valid: false, error: 'Form must have sections array' };
52
+ }
53
+
54
+ return { valid: true };
55
+ } catch (e) {
56
+ return { valid: false, error: 'Invalid form JSON structure' };
57
+ }
58
+ },
59
+
60
+ /**
61
+ * Validate appointments protocol configuration
62
+ * @param {Object} data - Appointments protocol data
63
+ * @returns {{ valid: boolean, error?: string }}
64
+ */
65
+ appointments: (data) => {
66
+ if (!data.destinations || data.destinations.length === 0) {
67
+ return { valid: false, error: 'At least one calendar destination is required' };
68
+ }
69
+
70
+ for (const dest of data.destinations) {
71
+ if (!dest.id || !dest.provider || !dest.popupUrl) {
72
+ return {
73
+ valid: false,
74
+ error: 'All calendars must have id, provider, and popupUrl',
75
+ };
76
+ }
77
+
78
+ const validProviders = ['calendly', 'cal.com'];
79
+ if (!validProviders.includes(dest.provider)) {
80
+ return {
81
+ valid: false,
82
+ error: `Invalid provider: ${dest.provider}. Must be one of: ${validProviders.join(', ')}`,
83
+ };
84
+ }
85
+ }
86
+
87
+ return { valid: true };
88
+ },
89
+
90
+ /**
91
+ * Validate triage protocol configuration
92
+ * @param {Object} data - Triage protocol data with routes array
93
+ * @returns {{ valid: boolean, error?: string }}
94
+ */
95
+ triage: (data) => {
96
+ // Support both { routes: [...] } structure and direct array (legacy)
97
+ const routes = Array.isArray(data) ? data : data?.routes;
98
+
99
+ if (!routes || !Array.isArray(routes) || routes.length === 0) {
100
+ return { valid: false, error: 'At least one triage route is required' };
101
+ }
102
+
103
+ for (const route of routes) {
104
+ if (!route.name || !route.description || !route.url) {
105
+ return {
106
+ valid: false,
107
+ error: 'All routes must have name, description, and url',
108
+ };
109
+ }
110
+
111
+ if (!route.deploymentId) {
112
+ return {
113
+ valid: false,
114
+ error: `Route "${route.name}" is missing deploymentId`,
115
+ };
116
+ }
117
+ }
118
+
119
+ return { valid: true };
120
+ },
121
+ };
122
+
123
+ /**
124
+ * Validate core configuration
125
+ * @param {Object} config - Core configuration
126
+ * @returns {{ valid: boolean, errors: string[] }}
127
+ */
128
+ export function validateCoreConfig(config) {
129
+ const errors = [];
130
+
131
+ if (!config.provider) errors.push('Provider is required');
132
+ if (!config.model) errors.push('Model is required');
133
+ // For inverted flow, apiKey is fetched at deployment time using apiKeyId
134
+ // Only require apiKey if not using inverted flow
135
+ if (!config._invertedFlow && !config.apiKey) {
136
+ errors.push('API Key is required');
137
+ }
138
+ // For inverted flow, require apiKeyId instead
139
+ if (config._invertedFlow && !config.apiKeyId) {
140
+ errors.push('API Key ID is required');
141
+ }
142
+ if (!config.botName) errors.push('Bot Name is required');
143
+
144
+ if (config.botName && !/^[a-z0-9-]+$/.test(config.botName)) {
145
+ errors.push('Bot name must contain only lowercase letters, numbers, and hyphens');
146
+ }
147
+
148
+ return {
149
+ valid: errors.length === 0,
150
+ errors,
151
+ };
152
+ }
153
+
154
+ /**
155
+ * Validate identity configuration
156
+ * @param {Object} config - Identity configuration
157
+ * @returns {{ valid: boolean, errors: string[] }}
158
+ */
159
+ export function validateIdentityConfig(config) {
160
+ const errors = [];
161
+
162
+ if (!config.objective) errors.push('Objective is required');
163
+ if (!config.firstMessage) errors.push('First Message is required');
164
+ if (!config.chatDisplayName) errors.push('Chat Display Name is required');
165
+
166
+ return {
167
+ valid: errors.length === 0,
168
+ errors,
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Validate that at least one protocol is enabled
174
+ * @param {Object} enabledProtocols - Protocol toggles
175
+ * @returns {{ valid: boolean, error?: string }}
176
+ */
177
+ export function validateEnabledProtocols(enabledProtocols) {
178
+ const hasEnabled =
179
+ enabledProtocols.knowledge ||
180
+ enabledProtocols.formGathering ||
181
+ enabledProtocols.appointments ||
182
+ enabledProtocols.triage;
183
+
184
+ return {
185
+ valid: hasEnabled,
186
+ error: hasEnabled ? null : 'At least one protocol must be enabled',
187
+ };
188
+ }
189
+
190
+ /**
191
+ * Validate session is ready for deployment
192
+ * @param {Object} session - Modular session object
193
+ * @returns {{ valid: boolean, errors: string[] }}
194
+ */
195
+ export function validateSessionForDeployment(session) {
196
+ const errors = [];
197
+
198
+ // Check core config
199
+ if (!session.coreConfig) {
200
+ errors.push('Core configuration is missing');
201
+ } else {
202
+ const coreValidation = validateCoreConfig(session.coreConfig);
203
+ if (!coreValidation.valid) {
204
+ errors.push(...coreValidation.errors);
205
+ }
206
+ }
207
+
208
+ // Check identity config
209
+ if (!session.identityConfig) {
210
+ errors.push('Identity configuration is missing');
211
+ } else {
212
+ const identityValidation = validateIdentityConfig(session.identityConfig);
213
+ if (!identityValidation.valid) {
214
+ errors.push(...identityValidation.errors);
215
+ }
216
+ }
217
+
218
+ // Check enabled protocols
219
+ const protocolValidation = validateEnabledProtocols(session.enabledProtocols);
220
+ if (!protocolValidation.valid) {
221
+ errors.push(protocolValidation.error);
222
+ }
223
+
224
+ // Validate each enabled protocol's data
225
+ for (const [protocol, enabled] of Object.entries(session.enabledProtocols)) {
226
+ if (enabled && PROTOCOL_VALIDATORS[protocol]) {
227
+ const protocolData = session.protocolData[protocol] || {};
228
+ const validation = PROTOCOL_VALIDATORS[protocol](protocolData);
229
+ if (!validation.valid) {
230
+ errors.push(`${protocol}: ${validation.error}`);
231
+ }
232
+ }
233
+ }
234
+
235
+ return {
236
+ valid: errors.length === 0,
237
+ errors,
238
+ };
239
+ }