feat: config-driven flow runtime engine for WhatsApp conversations

Groups + Blocks execution model adapted from Typebot:
- FlowExecutionService: walks through groups/blocks, pauses at InputBlocks
- FlowSessionService: Redis-backed session state (24h TTL)
- FlowStoreService: loads flow definitions from data/flows/ JSON files
- FlowVariableService: {{variable}} interpolation + expressions
- ToolRegistry: registered tool handlers (departments, doctors, slots, booking)
- Default appointment-booking.json flow seeded on first run

MessagingService delegates to flow engine when published flows exist,
falls back to hardcoded AI chat otherwise (backward compatible).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-20 18:27:29 +05:30
parent 4549241b78
commit 2e0527e1d8
9 changed files with 1201 additions and 3 deletions

View File

@@ -1,9 +1,10 @@
import { Injectable, Logger } from '@nestjs/common';
import { Injectable, Inject, Logger, Optional } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { generateText, tool, stepCountIs } from 'ai';
import { z } from 'zod';
import { MessagingProvider } from './providers/messaging-provider.interface';
import { MessagingConversationService } from './messaging-conversation.service';
import { FlowExecutionService } from './flow/flow-execution.service';
import { CallerResolutionService } from '../caller/caller-resolution.service';
import { CallerContextService } from '../caller/caller-context.service';
import { PlatformGraphqlService } from '../platform/platform-graphql.service';
@@ -27,6 +28,7 @@ export class MessagingService {
private callerContext: CallerContextService,
private platform: PlatformGraphqlService,
private aiConfig: AiConfigService,
@Optional() private flowExecution: FlowExecutionService,
) {
const cfg = aiConfig.getConfig();
this.aiModel = createAiModel({
@@ -51,6 +53,14 @@ export class MessagingService {
const replyId = message.interactiveReply?.id;
this.logger.log(`[WA] Inbound from ${phone} (${name}): ${text.substring(0, 100)}${replyId ? ` [reply_id=${replyId}]` : ''}`);
// Delegate to flow engine if published flows exist
if (this.flowExecution?.hasFlows()) {
this.logger.log(`[WA] Delegating to flow engine`);
await this.flowExecution.handleMessage(message);
return;
}
// Fallback: hardcoded AI chat (legacy — will be removed once flows are validated)
if (!this.aiModel) {
await this.provider.sendText(phone, 'Our assistant is temporarily unavailable. Please call us directly.');
return;