@ema.co/mcp-toolkit 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -238,12 +238,13 @@ export async function handlePersona(args, client, getTemplateId, createClientFor
238
238
  }
239
239
  // IMPORTANT: The Ema API requires workflow to be sent along with proto_config
240
240
  // for proto_config changes to persist. This matches what the UI does.
241
+ const workflowToSet = args.workflow ?? existingWorkflow;
241
242
  await client.updateAiEmployee({
242
243
  persona_id: persona.id,
243
244
  name: args.name,
244
245
  description: args.description,
245
246
  proto_config: mergedProtoConfig,
246
- workflow: existingWorkflow, // Include workflow for proto_config to persist
247
+ workflow: workflowToSet, // Use provided workflow or existing workflow for proto_config to persist
247
248
  enabled_by_user: typeof args.enabled === "boolean" ? args.enabled : undefined,
248
249
  });
249
250
  return {
@@ -544,7 +545,7 @@ export async function handlePersona(args, client, getTemplateId, createClientFor
544
545
  // ═══════════════════════════════════════════════════════════════════════════
545
546
  // WORKFLOW Handler - Unified greenfield/brownfield operations
546
547
  // ═══════════════════════════════════════════════════════════════════════════
547
- export async function handleWorkflow(args, client) {
548
+ export async function handleWorkflow(args, client, getTemplateId) {
548
549
  const personaId = args.persona_id;
549
550
  const workflowDef = args.workflow_def;
550
551
  const input = args.input;
@@ -570,8 +571,12 @@ export async function handleWorkflow(args, client) {
570
571
  // Auto-fix issues
571
572
  effectiveMode = "optimize";
572
573
  }
574
+ else if (personaId && workflowDef) {
575
+ // DEPLOY: Persona exists + workflow_def provided → deploy directly (no input needed)
576
+ effectiveMode = "deploy";
577
+ }
573
578
  else if (personaId && input) {
574
- // BROWNFIELD: Modify existing workflow
579
+ // MODIFY: Persona exists + input provided → generate new workflow from input
575
580
  effectiveMode = "modify"; // New unified mode for all brownfield changes
576
581
  }
577
582
  else if (input && !personaId) {
@@ -589,8 +594,10 @@ export async function handleWorkflow(args, client) {
589
594
  const legacyMode = args.mode;
590
595
  if (legacyMode === "extend")
591
596
  effectiveMode = "modify";
592
- if (legacyMode === "deploy" && personaId && input)
593
- effectiveMode = "modify";
597
+ if (legacyMode === "deploy") {
598
+ // If explicit deploy mode, use it (works with or without input)
599
+ effectiveMode = "deploy";
600
+ }
594
601
  switch (effectiveMode) {
595
602
  case "extraction_schema": {
596
603
  // Generate extraction schema from JSON metadata (fully dynamic, no hardcoding)
@@ -783,7 +790,95 @@ export async function handleWorkflow(args, client) {
783
790
  result.deployed_to = { persona_id: personaId, persona_name: persona.name };
784
791
  }
785
792
  else if (!preview && !personaId) {
786
- result.hint = "Provide persona_id to deploy, or use persona(mode='create') first";
793
+ // GREENFIELD: Create new persona from template, then update proto_config only
794
+ // The template provides a valid workflow structure - we don't replace it,
795
+ // we just configure the persona settings (voice config, welcome message, etc.)
796
+ const personaName = args.name;
797
+ if (personaName) {
798
+ const personaType = args.type || "chat";
799
+ const DEFAULT_TEMPLATES = {
800
+ voice: "00000000-0000-0000-0000-00000000001e", // Voice AI template
801
+ chat: "00000000-0000-0000-0000-000000000004", // Chat AI template
802
+ dashboard: "00000000-0000-0000-0000-000000000002", // Dashboard AI template
803
+ };
804
+ const templateId = getTemplateId?.(personaType) || DEFAULT_TEMPLATES[personaType];
805
+ if (!templateId) {
806
+ return {
807
+ error: `No template found for type "${personaType}". Provide template_id or use type: voice, chat, or dashboard.`
808
+ };
809
+ }
810
+ // Step 1: Create the persona from template
811
+ const createResult = await client.createAiEmployee({
812
+ name: personaName,
813
+ description: args.description,
814
+ template_id: templateId,
815
+ });
816
+ const newPersonaId = createResult.persona_id ?? createResult.id;
817
+ if (!newPersonaId) {
818
+ return { error: "Failed to create persona: no ID returned" };
819
+ }
820
+ // Step 2: Fetch the newly created persona to get template's valid structure
821
+ const newPersona = await client.getPersonaById(newPersonaId);
822
+ if (!newPersona) {
823
+ return { error: `Failed to fetch newly created persona: ${newPersonaId}` };
824
+ }
825
+ // Step 3: Merge proto_config - keep template structure, update widget values
826
+ const existingProtoConfig = (newPersona.proto_config ?? {});
827
+ const generatedProtoConfig = args.proto_config || compiled.proto_config || {};
828
+ // Merge widgets by name - template widgets provide structure, generated values override
829
+ const existingWidgets = (existingProtoConfig.widgets ?? []);
830
+ const generatedWidgets = (generatedProtoConfig.widgets ?? []);
831
+ const widgetMap = new Map();
832
+ // Start with template widgets (valid structure)
833
+ for (const w of existingWidgets) {
834
+ if (typeof w.name === "string" && w.name.trim().length > 0) {
835
+ widgetMap.set(w.name, w);
836
+ }
837
+ }
838
+ // Merge generated widget VALUES into template structure
839
+ for (const genWidget of generatedWidgets) {
840
+ const widgetName = genWidget.name;
841
+ if (typeof widgetName === "string" && widgetName.trim().length > 0) {
842
+ const existing = widgetMap.get(widgetName);
843
+ if (existing) {
844
+ // Merge: keep template structure, update config values
845
+ const merged = { ...existing };
846
+ // The widget config is stored under the widget name (e.g., conversationSettings)
847
+ if (genWidget[widgetName]) {
848
+ merged[widgetName] = { ...(existing[widgetName] || {}), ...genWidget[widgetName] };
849
+ }
850
+ widgetMap.set(widgetName, merged);
851
+ }
852
+ else {
853
+ // New widget not in template - add as-is
854
+ widgetMap.set(widgetName, genWidget);
855
+ }
856
+ }
857
+ }
858
+ const mergedProtoConfig = {
859
+ ...existingProtoConfig,
860
+ widgets: Array.from(widgetMap.values()),
861
+ };
862
+ // Step 4: Update persona with merged proto_config only
863
+ // DO NOT send workflow - keep the template's valid workflow structure
864
+ await client.updateAiEmployee({
865
+ persona_id: newPersonaId,
866
+ proto_config: mergedProtoConfig,
867
+ });
868
+ result.deployed_to = {
869
+ persona_id: newPersonaId,
870
+ persona_name: personaName,
871
+ created: true
872
+ };
873
+ result.status = "deployed";
874
+ result.next_steps = [
875
+ `Persona "${personaName}" created with template workflow.`,
876
+ `To customize the workflow, use: workflow(persona_id="${newPersonaId}", input="add search node", preview=false)`,
877
+ ];
878
+ }
879
+ else {
880
+ result.hint = "Provide name to create new persona, or persona_id to deploy to existing persona";
881
+ }
787
882
  }
788
883
  if (preview) {
789
884
  result.next_steps = [
@@ -959,8 +1054,68 @@ export async function handleWorkflow(args, client) {
959
1054
  }
960
1055
  return result;
961
1056
  }
962
- // Note: mode="deploy" is now handled by preview=false on extend/optimize/generate
963
- // Legacy calls are converted at the top of the handler
1057
+ case "deploy": {
1058
+ // DEPLOY: Direct workflow deployment to existing persona (no input required)
1059
+ if (!personaId) {
1060
+ return { error: "persona_id required for deploy mode" };
1061
+ }
1062
+ if (!workflowDef) {
1063
+ return { error: "workflow_def required for deploy mode" };
1064
+ }
1065
+ const persona = await client.getPersonaById(personaId);
1066
+ if (!persona) {
1067
+ return { error: `Persona not found: ${personaId}` };
1068
+ }
1069
+ // Sanitize workflow before deployment
1070
+ const sanitizedWorkflow = sanitizeWorkflowForDeploy(workflowDef);
1071
+ // Determine proto_config to use: provided > existing
1072
+ const existingProtoConfig = persona.proto_config;
1073
+ const providedProtoConfig = args.proto_config;
1074
+ const protoConfigToUse = providedProtoConfig || existingProtoConfig || {};
1075
+ // Check if this is a voice persona and warn if critical settings are missing
1076
+ const projectSettings = protoConfigToUse.projectSettings;
1077
+ const isVoice = projectSettings?.projectType === 5;
1078
+ const warnings = [];
1079
+ if (isVoice && !providedProtoConfig) {
1080
+ // Check if existing proto_config has conversationSettings populated
1081
+ const widgets = (protoConfigToUse.widgets ?? []);
1082
+ const convSettings = widgets.find(w => w.name === "conversationSettings");
1083
+ const convConfig = convSettings?.conversationSettings;
1084
+ if (!convConfig?.welcomeMessage || !convConfig?.identityAndPurpose) {
1085
+ warnings.push("Voice persona detected but proto_config not provided. Voice settings (welcomeMessage, identityAndPurpose, etc.) may be empty or generic. " +
1086
+ "If you generated the workflow with workflow(input=...), pass the generated proto_config along with workflow_def to preserve voice settings.");
1087
+ }
1088
+ }
1089
+ // Deploy workflow (always deploy, preview is not applicable for explicit deploy mode)
1090
+ try {
1091
+ await client.updateAiEmployee({
1092
+ persona_id: personaId,
1093
+ workflow: sanitizedWorkflow,
1094
+ proto_config: protoConfigToUse,
1095
+ });
1096
+ const result = {
1097
+ mode: "deploy",
1098
+ status: "deployed",
1099
+ persona_id: personaId,
1100
+ persona_name: persona.name,
1101
+ workflow_deployed: true,
1102
+ };
1103
+ if (warnings.length > 0) {
1104
+ result.warnings = warnings;
1105
+ }
1106
+ return result;
1107
+ }
1108
+ catch (err) {
1109
+ const errorMessage = err instanceof Error ? err.message : String(err);
1110
+ return {
1111
+ mode: "deploy",
1112
+ status: "failed",
1113
+ persona_id: personaId,
1114
+ persona_name: persona.name,
1115
+ error: errorMessage,
1116
+ };
1117
+ }
1118
+ }
964
1119
  case "compare": {
965
1120
  if (!personaId || !args.compare_to) {
966
1121
  return { error: "persona_id and compare_to required for compare mode" };
@@ -1147,12 +1147,12 @@ Now begin by reading the resources, then proceed with generation.`,
1147
1147
  ],
1148
1148
  },
1149
1149
  // ─────────────────────────────────────────────────────────────────────────
1150
- // Direct Workflow Generation (Bypasses Auto Builder)
1150
+ // Direct Workflow Generation
1151
1151
  // ─────────────────────────────────────────────────────────────────────────
1152
1152
  workflow_generate: {
1153
1153
  definition: {
1154
1154
  name: "workflow_generate",
1155
- description: "Generate a complete workflow_def and proto_config directly, bypassing Auto Builder. Produces ready-to-deploy JSON configurations.",
1155
+ description: "Generate an AI Employee workflow. Preview first, then deploy when ready.",
1156
1156
  arguments: [
1157
1157
  {
1158
1158
  name: "name",
@@ -1161,29 +1161,14 @@ Now begin by reading the resources, then proceed with generation.`,
1161
1161
  },
1162
1162
  {
1163
1163
  name: "description",
1164
- description: "What the AI Employee does",
1164
+ description: "What the AI Employee does (be detailed about capabilities, intents, data sources)",
1165
1165
  required: true,
1166
1166
  },
1167
1167
  {
1168
- name: "persona_type",
1168
+ name: "type",
1169
1169
  description: "Type: 'voice', 'chat', or 'dashboard'",
1170
1170
  required: true,
1171
1171
  },
1172
- {
1173
- name: "pattern",
1174
- description: "Workflow pattern: 'kb_search' (FAQ/docs), 'intent_routing' (multi-intent), 'tool_calling' (external actions)",
1175
- required: true,
1176
- },
1177
- {
1178
- name: "intents",
1179
- description: "For intent_routing: comma-separated intent names with handler type, e.g., 'Search:search,Help:llm,Transfer:fixed'",
1180
- required: false,
1181
- },
1182
- {
1183
- name: "tools",
1184
- description: "For tool_calling: comma-separated tool names with namespace, e.g., 'Create_Ticket:service_now,Send_Email:outlook'",
1185
- required: false,
1186
- },
1187
1172
  ],
1188
1173
  },
1189
1174
  render: (args) => [
@@ -1191,59 +1176,45 @@ Now begin by reading the resources, then proceed with generation.`,
1191
1176
  role: "user",
1192
1177
  content: {
1193
1178
  type: "text",
1194
- text: `Generate a complete workflow configuration for: ${args.name}
1179
+ text: `Generate AI Employee: ${args.name}
1195
1180
 
1181
+ **Type**: ${args.type}
1196
1182
  **Description**: ${args.description}
1197
- **Persona Type**: ${args.persona_type}
1198
- **Pattern**: ${args.pattern}
1199
- ${args.intents ? `**Intents**: ${args.intents}` : ""}
1200
- ${args.tools ? `**Tools**: ${args.tools}` : ""}
1201
-
1202
- ## Instructions
1203
-
1204
- Generate deployment-ready JSON using the consolidated \`workflow\` tool (local compiler):
1205
1183
 
1206
- ### Step 1: Generate workflow_def + proto_config (Direct / Deterministic)
1207
- Call:
1208
- \`workflow(input=<requirements>, type="${args.persona_type}", use_autobuilder=false)\`
1184
+ ## Step 1: Preview the workflow
1209
1185
 
1210
- Where \`<requirements>\` includes:
1211
- - Name: ${args.name}
1212
- - Description: ${args.description}
1213
- - Pattern hint: ${args.pattern}
1214
- ${args.intents ? `- Intents: ${args.intents}` : ""}
1215
- ${args.tools ? `- Tools: ${args.tools}` : ""}
1216
-
1217
- **HITL Policy**: DEFAULT is no approval gates. Only add HITL if user explicitly requests approval/confirmation before external actions.
1218
-
1219
- If the tool returns \`status="needs_input"\`, ask the missing questions, then call \`workflow(...)\` again with the additional details.
1220
-
1221
- ### Step 2: (Optional) Validate the generated workflow
1222
- Call \`workflow(mode="analyze", workflow_def=<workflow_def>)\` and fix any critical issues before deploying.
1186
+ \`\`\`
1187
+ workflow(
1188
+ input="${args.description}",
1189
+ type="${args.type}"
1190
+ )
1191
+ \`\`\`
1223
1192
 
1224
- ### Step 3: Deploy (Optional)
1225
- If you already have a persona, deploy directly:
1226
- \`workflow(mode="deploy", persona_id="<persona_id>", workflow_def=<workflow_def>, proto_config=<proto_config>)\`
1193
+ This returns workflow_def and proto_config. Review them before deploying.
1227
1194
 
1228
- If you need to create one first:
1229
- \`persona(mode="create", name="${args.name}", type="${args.persona_type}")\`
1230
- then deploy as above.
1195
+ ## Step 2: Deploy when ready
1231
1196
 
1232
- ## Output Format
1197
+ \`\`\`
1198
+ workflow(
1199
+ input="${args.description}",
1200
+ name="${args.name}",
1201
+ type="${args.type}",
1202
+ preview=false
1203
+ )
1204
+ \`\`\`
1233
1205
 
1234
- Provide the final configurations in this format:
1206
+ This creates the persona and deploys the workflow in one step.
1235
1207
 
1236
- \`\`\`json
1237
- // workflow_def.json
1238
- {workflow_def}
1239
- \`\`\`
1208
+ ${args.type === "voice" ? `
1209
+ ## Voice AI Notes
1240
1210
 
1241
- \`\`\`json
1242
- // proto_config.json
1243
- {proto_config}
1244
- \`\`\`
1211
+ The generated proto_config includes voice settings:
1212
+ - welcomeMessage: Generated greeting
1213
+ - identityAndPurpose: Generated from description
1214
+ - takeActionInstructions, hangupInstructions: Sensible defaults
1245
1215
 
1246
- Now generate by calling \`workflow(input=<requirements>, type="${args.persona_type}", use_autobuilder=false)\`.`,
1216
+ These are set automatically but can be customized via proto_config override.
1217
+ ` : ""}`,
1247
1218
  },
1248
1219
  },
1249
1220
  ],
@@ -1251,7 +1222,7 @@ Now generate by calling \`workflow(input=<requirements>, type="${args.persona_ty
1251
1222
  workflow_deploy: {
1252
1223
  definition: {
1253
1224
  name: "workflow_deploy",
1254
- description: "Deploy a generated workflow to an Ema environment. Takes workflow_def and proto_config and creates/updates an AI Employee.",
1225
+ description: "Create a new AI Employee from template with configured settings, or modify an existing one.",
1255
1226
  arguments: [
1256
1227
  {
1257
1228
  name: "name",
@@ -1259,13 +1230,18 @@ Now generate by calling \`workflow(input=<requirements>, type="${args.persona_ty
1259
1230
  required: true,
1260
1231
  },
1261
1232
  {
1262
- name: "env",
1263
- description: "Target environment (default: demo)",
1264
- required: false,
1233
+ name: "type",
1234
+ description: "Type: voice, chat, or dashboard",
1235
+ required: true,
1236
+ },
1237
+ {
1238
+ name: "description",
1239
+ description: "What this AI Employee does",
1240
+ required: true,
1265
1241
  },
1266
1242
  {
1267
- name: "update_existing",
1268
- description: "If 'true', update existing AI Employee with same name instead of creating new",
1243
+ name: "env",
1244
+ description: "Target environment (default: demo)",
1269
1245
  required: false,
1270
1246
  },
1271
1247
  ],
@@ -1275,70 +1251,51 @@ Now generate by calling \`workflow(input=<requirements>, type="${args.persona_ty
1275
1251
  role: "user",
1276
1252
  content: {
1277
1253
  type: "text",
1278
- text: `Deploy workflow configuration for: ${args.name}
1279
-
1280
- **Target Environment**: ${args.env || "demo"}
1281
- **Update Existing**: ${args.update_existing === "true" ? "Yes" : "No (create new)"}
1282
-
1283
- ## Instructions
1284
-
1285
- ### Pre-Deploy Checklist
1286
-
1287
- Before deploying, ensure you have:
1288
- 1. ✅ Generated workflow_def using workflow_generate prompt
1289
- 2. ✅ Generated proto_config/persona settings
1290
- 3. ✅ Validated the workflow (no critical issues)
1291
-
1292
- ### Step 1: Check for Existing
1254
+ text: `Create AI Employee: ${args.name}
1293
1255
 
1294
- ${args.update_existing === "true" ? `
1295
- Call \`persona(id="${args.name}", env="${args.env || "demo"}")\`
1296
- to find the existing AI Employee (and capture its persona ID).
1297
- ` : `
1298
- Call \`persona(query="${args.name}", env="${args.env || "demo"}")\`
1299
- to check whether an AI Employee with this name already exists (and capture the persona ID if present).
1300
- `}
1301
-
1302
- ### Step 2: Create or Update
1303
-
1304
- ${args.update_existing === "true" ? `
1305
- If not found, create it:
1306
- \`persona(mode="create", name="${args.name}", type="chat", env="${args.env || "demo"}")\`
1307
- ` : `
1308
- Create a new AI Employee:
1309
- \`persona(mode="create", name="${args.name}", type="chat", env="${args.env || "demo"}")\`
1310
- `}
1311
-
1312
- ### Step 3: Configure Workflow
1313
-
1314
- Deploy the workflow via MCP:
1315
- \`workflow(mode="deploy", persona_id="<persona_id>", workflow_def=<workflow_def>, proto_config=<proto_config>, env="${args.env || "demo"}")\`
1256
+ **Type**: ${args.type || "chat"}
1257
+ **Environment**: ${args.env || "demo"}
1258
+ **Description**: ${args.description}
1316
1259
 
1317
- ### Step 4: Test
1260
+ ## How It Works
1318
1261
 
1319
- After deployment:
1320
- 1. Open the AI Employee in simulator
1321
- 2. Test each intent/path
1322
- 3. Verify responses are correct
1323
- 4. Check HITL flows work (if applicable)
1262
+ **Greenfield** (new personas): Creates from template, configures settings.
1263
+ - Template provides valid workflow structure
1264
+ - We configure proto_config (voice settings, welcome message, etc.)
1265
+ - Customize workflow AFTER creation via modify mode
1324
1266
 
1325
- ### Step 5: Activate
1267
+ **Brownfield** (existing personas): Uses LLM-native transformation.
1268
+ - Fetches existing workflow
1269
+ - Decompiles to WorkflowSpec
1270
+ - Transforms based on your input
1271
+ - Compiles back and deploys
1326
1272
 
1327
- Once tested:
1328
- 1. Call \`persona(id="<persona_id>", mode="update", enabled=true, env="${args.env || "demo"}")\`
1329
- 2. Monitor initial conversations
1330
- 3. Iterate based on feedback
1273
+ ## Create New Persona
1331
1274
 
1332
- ## Deployment Summary
1275
+ \`\`\`
1276
+ workflow(
1277
+ input="${args.description}",
1278
+ name="${args.name}",
1279
+ type="${args.type || "chat"}",
1280
+ preview=false,
1281
+ env="${args.env || "demo"}"
1282
+ )
1283
+ \`\`\`
1333
1284
 
1334
- Provide a deployment summary including:
1335
- - AI Employee ID (if created/updated)
1336
- - Environment
1337
- - Workflow configuration details
1338
- - Test results
1339
- - Next steps
1285
+ This creates the persona from template and configures settings.
1286
+ ${args.type === "voice" ? `
1287
+ For Voice AI, this sets:
1288
+ - welcomeMessage (generated greeting)
1289
+ - identityAndPurpose (from your description)
1290
+ - hangupInstructions, speechCharacteristics (defaults)
1291
+ ` : ""}
1292
+ ## After Creation
1340
1293
 
1341
- Now proceed with deployment.`,
1294
+ 1. Get persona_id from response
1295
+ 2. Verify: \`persona(id="<persona_id>", include_workflow=true)\`
1296
+ 3. Customize workflow: \`workflow(persona_id="<id>", input="add search node")\`
1297
+ 4. Test in Ema simulator
1298
+ 5. Enable: \`persona(id="<id>", mode="update", enabled=true)\``,
1342
1299
  },
1343
1300
  },
1344
1301
  ],
@@ -4843,7 +4843,12 @@ const toolHandlers = {
4843
4843
  // Consolidated workflow handler (replaces legacy inline handler)
4844
4844
  workflow: async (args) => {
4845
4845
  const client = createClient(args.env);
4846
- return handleWorkflow(args, client);
4846
+ const DEFAULT_TEMPLATES = {
4847
+ voice: "00000000-0000-0000-0000-00000000001e", // Voice AI template
4848
+ chat: "00000000-0000-0000-0000-000000000004", // Chat AI template
4849
+ dashboard: "00000000-0000-0000-0000-000000000002", // Dashboard AI template
4850
+ };
4851
+ return handleWorkflow(args, client, (type) => DEFAULT_TEMPLATES[type]);
4847
4852
  },
4848
4853
  action: async (args) => {
4849
4854
  const client = createClient(args.env);
@@ -4901,14 +4906,16 @@ toolHandlers.workflow = async (args) => {
4901
4906
  const rawMode = normalizedArgs.mode ? String(normalizedArgs.mode) : undefined;
4902
4907
  const mode = rawMode === "improve" ? "optimize" : rawMode;
4903
4908
  // Default mode selection:
4904
- // - persona_id + inputoptimize (BROWNFIELD: enhance existing workflow)
4909
+ // - persona_id + workflow_defdeploy (direct deployment, no input needed)
4910
+ // - persona_id + input → modify (BROWNFIELD: generate new workflow from input)
4905
4911
  // - persona_id only or workflow_def → analyze (inspect)
4906
4912
  // - input only → generate (GREENFIELD: create from scratch)
4907
4913
  // - Otherwise → generate (and prompt for missing info)
4908
4914
  const effectiveMode = mode ??
4909
- (personaId && inputProvided ? "optimize" : // BROWNFIELD: existing persona + new requirements
4910
- workflowDef || personaId ? "analyze" : // Inspect existing
4911
- "generate"); // GREENFIELD: new workflow
4915
+ (personaId && workflowDef ? "deploy" : // Direct deployment (no input required)
4916
+ personaId && inputProvided ? "modify" : // BROWNFIELD: existing persona + new requirements
4917
+ workflowDef || personaId ? "analyze" : // Inspect existing
4918
+ "generate"); // GREENFIELD: new workflow
4912
4919
  switch (effectiveMode) {
4913
4920
  case "generate": {
4914
4921
  const result = await legacyWorkflowTool({ ...normalizedArgs, mode: "generate" });
@@ -5137,11 +5144,131 @@ toolHandlers.sync = async (args) => {
5137
5144
  include_status: normalizedArgs.include_status,
5138
5145
  });
5139
5146
  };
5140
- // Consolidated demo tool: consolidate | generate | validate | template
5147
+ // Consolidated demo tool: kit | validate_kit | scenarios | consolidate | generate | validate | template
5141
5148
  toolHandlers.demo = async (args) => {
5142
5149
  const normalizedArgs = { ...(args ?? {}) };
5143
5150
  const mode = normalizedArgs.mode ? String(normalizedArgs.mode) : "template";
5144
5151
  switch (mode) {
5152
+ case "kit": {
5153
+ // Generate complete demo kit for a persona
5154
+ const personaId = String(normalizedArgs.persona_id ?? "");
5155
+ const scenarioId = String(normalizedArgs.scenario ?? "sales-sdr");
5156
+ if (!personaId) {
5157
+ throw new Error('demo(mode="kit") requires: persona_id');
5158
+ }
5159
+ // Import demo generator
5160
+ const { generateDemoKit, DEMO_SCENARIOS, generateDemoScriptMarkdown, validateDemoKit } = await import("../sdk/demo-generator.js");
5161
+ // Get scenario
5162
+ const scenario = DEMO_SCENARIOS[scenarioId];
5163
+ if (!scenario) {
5164
+ throw new Error(`Unknown scenario: ${scenarioId}. Available: ${Object.keys(DEMO_SCENARIOS).join(", ")}`);
5165
+ }
5166
+ // Get persona and workflow
5167
+ const client = await createClient(normalizedArgs.env);
5168
+ const persona = await client.getPersonaById(personaId);
5169
+ if (!persona) {
5170
+ throw new Error(`Persona not found: ${personaId}`);
5171
+ }
5172
+ const workflowDef = persona.workflow_def || {};
5173
+ const customQA = normalizedArgs.custom_qa;
5174
+ // Generate kit
5175
+ const kit = generateDemoKit(personaId, persona.name || personaId, workflowDef, scenario, customQA);
5176
+ // Generate markdown script
5177
+ const demoScript = generateDemoScriptMarkdown(kit);
5178
+ // Validate
5179
+ const validation = validateDemoKit(kit);
5180
+ return {
5181
+ success: true,
5182
+ persona_id: personaId,
5183
+ persona_name: persona.name,
5184
+ scenario: scenarioId,
5185
+ kit_summary: {
5186
+ kb_documents: kit.kb_documents.length,
5187
+ demo_questions: kit.demo_script.length,
5188
+ fixed_responses: kit.fixed_responses.length,
5189
+ validation_queries: kit.validation_queries.length,
5190
+ },
5191
+ validation,
5192
+ demo_script_preview: demoScript.slice(0, 2000) + (demoScript.length > 2000 ? "\n\n... (truncated)" : ""),
5193
+ kit,
5194
+ instructions: [
5195
+ "1. Upload KB documents to the persona's knowledge base",
5196
+ "2. Review the demo script and practice the questions",
5197
+ "3. Optionally apply fixed_responses for guaranteed fallbacks",
5198
+ "4. Run validation queries to verify demo readiness",
5199
+ "5. Conduct the demo with confidence!",
5200
+ ],
5201
+ };
5202
+ }
5203
+ case "validate_kit": {
5204
+ // Validate a persona's demo readiness
5205
+ const personaId = String(normalizedArgs.persona_id ?? "");
5206
+ if (!personaId) {
5207
+ throw new Error('demo(mode="validate_kit") requires: persona_id');
5208
+ }
5209
+ const { analyzeWorkflowForDemo, DEMO_SCENARIOS } = await import("../sdk/demo-generator.js");
5210
+ const client = await createClient(normalizedArgs.env);
5211
+ const persona = await client.getPersonaById(personaId);
5212
+ if (!persona) {
5213
+ throw new Error(`Persona not found: ${personaId}`);
5214
+ }
5215
+ const analysis = analyzeWorkflowForDemo(persona.workflow_def || {});
5216
+ // Check data sources
5217
+ const dataSourcesResult = await client.listDataSourceFiles(personaId);
5218
+ const dataSources = dataSourcesResult.files || [];
5219
+ const hasKnowledgeBase = dataSources.length > 0;
5220
+ const issues = [];
5221
+ if (!hasKnowledgeBase) {
5222
+ issues.push("No knowledge base documents uploaded - RAG search will fail");
5223
+ }
5224
+ if (analysis.intents.length === 0) {
5225
+ issues.push("No categorizer intents detected - workflow may not route correctly");
5226
+ }
5227
+ if (!analysis.has_search) {
5228
+ issues.push("No search nodes detected - cannot retrieve KB data");
5229
+ }
5230
+ // Suggest best scenario
5231
+ let suggestedScenario = "sales-sdr";
5232
+ for (const [id, scenario] of Object.entries(DEMO_SCENARIOS)) {
5233
+ const intentOverlap = scenario.intents.filter(i => analysis.intents.some(ai => ai.toLowerCase().includes(i.name.toLowerCase()))).length;
5234
+ if (intentOverlap > 0) {
5235
+ suggestedScenario = id;
5236
+ break;
5237
+ }
5238
+ }
5239
+ return {
5240
+ persona_id: personaId,
5241
+ persona_name: persona.name,
5242
+ ready: issues.length === 0,
5243
+ issues,
5244
+ workflow_analysis: analysis,
5245
+ knowledge_base: {
5246
+ has_documents: hasKnowledgeBase,
5247
+ document_count: dataSources.length,
5248
+ },
5249
+ suggested_scenario: suggestedScenario,
5250
+ next_steps: issues.length > 0
5251
+ ? issues.map((issue, i) => `${i + 1}. Fix: ${issue}`)
5252
+ : [`Generate demo kit: demo(mode="kit", persona_id="${personaId}", scenario="${suggestedScenario}")`],
5253
+ };
5254
+ }
5255
+ case "scenarios": {
5256
+ // List available demo scenarios
5257
+ const { DEMO_SCENARIOS } = await import("../sdk/demo-generator.js");
5258
+ return {
5259
+ scenarios: Object.entries(DEMO_SCENARIOS).map(([id, scenario]) => ({
5260
+ id,
5261
+ name: scenario.name,
5262
+ description: scenario.description,
5263
+ persona_types: scenario.persona_types,
5264
+ tags: scenario.tags,
5265
+ intent_count: scenario.intents.length,
5266
+ qa_count: scenario.qa_pairs.length,
5267
+ entity_types: scenario.entities.map(e => e.type),
5268
+ })),
5269
+ usage: 'demo(mode="kit", persona_id="...", scenario="<scenario_id>")',
5270
+ };
5271
+ }
5145
5272
  case "consolidate": {
5146
5273
  const source = String(normalizedArgs.source ?? "");
5147
5274
  const output = String(normalizedArgs.output ?? "");