@nextsparkjs/theme-default 0.1.0-beta.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 (333) hide show
  1. package/about/business.md +49 -0
  2. package/about/features.json +302 -0
  3. package/about/team.md +79 -0
  4. package/api/ai/chat/stream/route.ts +212 -0
  5. package/api/ai/orchestrator/route.ts +226 -0
  6. package/api/ai/single-agent/route.ts +291 -0
  7. package/api/ai/usage/route.ts +122 -0
  8. package/blocks/benefits/component.tsx +100 -0
  9. package/blocks/benefits/config.ts +11 -0
  10. package/blocks/benefits/examples.ts +85 -0
  11. package/blocks/benefits/fields.ts +156 -0
  12. package/blocks/benefits/schema.ts +33 -0
  13. package/blocks/cta-section/component.tsx +100 -0
  14. package/blocks/cta-section/config.ts +11 -0
  15. package/blocks/cta-section/examples.ts +41 -0
  16. package/blocks/cta-section/fields.ts +89 -0
  17. package/blocks/cta-section/index.ts +6 -0
  18. package/blocks/cta-section/schema.ts +32 -0
  19. package/blocks/cta-section/thumbnail.png +1 -0
  20. package/blocks/faq-accordion/component.tsx +156 -0
  21. package/blocks/faq-accordion/config.ts +11 -0
  22. package/blocks/faq-accordion/examples.ts +77 -0
  23. package/blocks/faq-accordion/fields.ts +119 -0
  24. package/blocks/faq-accordion/index.ts +6 -0
  25. package/blocks/faq-accordion/schema.ts +45 -0
  26. package/blocks/features-grid/component.tsx +112 -0
  27. package/blocks/features-grid/config.ts +11 -0
  28. package/blocks/features-grid/examples.ts +63 -0
  29. package/blocks/features-grid/fields.ts +97 -0
  30. package/blocks/features-grid/index.ts +6 -0
  31. package/blocks/features-grid/schema.ts +40 -0
  32. package/blocks/features-grid/thumbnail.png +1 -0
  33. package/blocks/hero/component.tsx +100 -0
  34. package/blocks/hero/config.ts +11 -0
  35. package/blocks/hero/examples.ts +35 -0
  36. package/blocks/hero/fields.ts +60 -0
  37. package/blocks/hero/index.ts +6 -0
  38. package/blocks/hero/schema.ts +32 -0
  39. package/blocks/hero/thumbnail.png +1 -0
  40. package/blocks/hero/thumbnail.png.txt +6 -0
  41. package/blocks/hero-with-form/component.tsx +232 -0
  42. package/blocks/hero-with-form/config.ts +11 -0
  43. package/blocks/hero-with-form/examples.ts +16 -0
  44. package/blocks/hero-with-form/fields.ts +207 -0
  45. package/blocks/hero-with-form/index.ts +6 -0
  46. package/blocks/hero-with-form/schema.ts +54 -0
  47. package/blocks/jumbotron/component.tsx +136 -0
  48. package/blocks/jumbotron/config.ts +11 -0
  49. package/blocks/jumbotron/examples.ts +36 -0
  50. package/blocks/jumbotron/fields.ts +202 -0
  51. package/blocks/jumbotron/index.ts +6 -0
  52. package/blocks/jumbotron/schema.ts +55 -0
  53. package/blocks/logo-cloud/component.tsx +154 -0
  54. package/blocks/logo-cloud/config.ts +11 -0
  55. package/blocks/logo-cloud/examples.ts +34 -0
  56. package/blocks/logo-cloud/fields.ts +133 -0
  57. package/blocks/logo-cloud/index.ts +6 -0
  58. package/blocks/logo-cloud/schema.ts +46 -0
  59. package/blocks/post-content/component.tsx +197 -0
  60. package/blocks/post-content/config.ts +11 -0
  61. package/blocks/post-content/examples.ts +33 -0
  62. package/blocks/post-content/fields.ts +165 -0
  63. package/blocks/post-content/index.ts +4 -0
  64. package/blocks/post-content/schema.ts +46 -0
  65. package/blocks/pricing-table/component.tsx +154 -0
  66. package/blocks/pricing-table/config.ts +11 -0
  67. package/blocks/pricing-table/examples.ts +96 -0
  68. package/blocks/pricing-table/fields.ts +161 -0
  69. package/blocks/pricing-table/index.ts +4 -0
  70. package/blocks/pricing-table/schema.ts +50 -0
  71. package/blocks/split-content/component.tsx +135 -0
  72. package/blocks/split-content/config.ts +11 -0
  73. package/blocks/split-content/examples.ts +38 -0
  74. package/blocks/split-content/fields.ts +198 -0
  75. package/blocks/split-content/index.ts +6 -0
  76. package/blocks/split-content/schema.ts +67 -0
  77. package/blocks/stats-counter/component.tsx +124 -0
  78. package/blocks/stats-counter/config.ts +11 -0
  79. package/blocks/stats-counter/examples.ts +61 -0
  80. package/blocks/stats-counter/fields.ts +134 -0
  81. package/blocks/stats-counter/index.ts +6 -0
  82. package/blocks/stats-counter/schema.ts +47 -0
  83. package/blocks/testimonials/component.tsx +114 -0
  84. package/blocks/testimonials/config.ts +11 -0
  85. package/blocks/testimonials/examples.ts +65 -0
  86. package/blocks/testimonials/fields.ts +105 -0
  87. package/blocks/testimonials/index.ts +6 -0
  88. package/blocks/testimonials/schema.ts +41 -0
  89. package/blocks/testimonials/thumbnail.png +1 -0
  90. package/blocks/text-content/component.tsx +97 -0
  91. package/blocks/text-content/config.ts +11 -0
  92. package/blocks/text-content/examples.ts +30 -0
  93. package/blocks/text-content/fields.ts +88 -0
  94. package/blocks/text-content/index.ts +6 -0
  95. package/blocks/text-content/schema.ts +30 -0
  96. package/blocks/text-content/thumbnail.png +1 -0
  97. package/blocks/timeline/component.tsx +267 -0
  98. package/blocks/timeline/config.ts +11 -0
  99. package/blocks/timeline/examples.ts +68 -0
  100. package/blocks/timeline/fields.ts +147 -0
  101. package/blocks/timeline/index.ts +6 -0
  102. package/blocks/timeline/schema.ts +49 -0
  103. package/blocks/video-hero/component.tsx +270 -0
  104. package/blocks/video-hero/config.ts +11 -0
  105. package/blocks/video-hero/examples.ts +24 -0
  106. package/blocks/video-hero/fields.ts +98 -0
  107. package/blocks/video-hero/index.ts +6 -0
  108. package/blocks/video-hero/schema.ts +39 -0
  109. package/components/ai-chat/ChatPanel.tsx +575 -0
  110. package/components/ai-chat/ConversationItem.tsx +266 -0
  111. package/components/ai-chat/ConversationSidebar.tsx +99 -0
  112. package/components/ai-chat/MarkdownRenderer.tsx +15 -0
  113. package/components/ai-chat/Message.tsx +42 -0
  114. package/components/ai-chat/MessageInput.tsx +49 -0
  115. package/components/ai-chat/MessageList.tsx +46 -0
  116. package/components/ai-chat/TypingIndicator.tsx +11 -0
  117. package/config/app.config.ts +367 -0
  118. package/config/billing.config.ts +349 -0
  119. package/config/dashboard.config.ts +506 -0
  120. package/config/dev.config.ts +104 -0
  121. package/config/features.config.ts +203 -0
  122. package/config/flows.config.ts +129 -0
  123. package/config/permissions.config.ts +245 -0
  124. package/config/theme.config.ts +74 -0
  125. package/docs/01-overview/01-introduction.md +335 -0
  126. package/docs/01-overview/02-customization.md +671 -0
  127. package/docs/02-features/01-components.md +155 -0
  128. package/docs/02-features/02-styling.md +139 -0
  129. package/docs/02-features/03-tasks-entity.md +407 -0
  130. package/docs/03-ai/01-overview.md +211 -0
  131. package/docs/03-ai/02-customization.md +436 -0
  132. package/entities/customers/customers.config.ts +75 -0
  133. package/entities/customers/customers.fields.ts +165 -0
  134. package/entities/customers/customers.service.ts +516 -0
  135. package/entities/customers/customers.types.ts +83 -0
  136. package/entities/customers/messages/en.json +66 -0
  137. package/entities/customers/messages/es.json +66 -0
  138. package/entities/customers/migrations/001_customers_table.sql +102 -0
  139. package/entities/customers/migrations/002_customers_metas.sql +92 -0
  140. package/entities/pages/messages/en.json +41 -0
  141. package/entities/pages/messages/es.json +41 -0
  142. package/entities/pages/migrations/001_pages_table.sql +112 -0
  143. package/entities/pages/migrations/002_pages_metas.sql +56 -0
  144. package/entities/pages/migrations/003_add_status.sql +50 -0
  145. package/entities/pages/pages-management.service.ts +610 -0
  146. package/entities/pages/pages.config.ts +94 -0
  147. package/entities/pages/pages.fields.ts +101 -0
  148. package/entities/pages/pages.service.ts +290 -0
  149. package/entities/pages/pages.types.ts +124 -0
  150. package/entities/posts/components/post-header.tsx +97 -0
  151. package/entities/posts/messages/en.json +55 -0
  152. package/entities/posts/messages/es.json +55 -0
  153. package/entities/posts/migrations/001_posts_table.sql +115 -0
  154. package/entities/posts/migrations/003_add_status.sql +44 -0
  155. package/entities/posts/migrations/004_entity_taxonomy_relations.sql +129 -0
  156. package/entities/posts/migrations/006_posts_metas.sql +56 -0
  157. package/entities/posts/posts.config.ts +101 -0
  158. package/entities/posts/posts.fields.ts +116 -0
  159. package/entities/posts/posts.service.ts +376 -0
  160. package/entities/posts/posts.types.ts +74 -0
  161. package/entities/tasks/messages/en.json +204 -0
  162. package/entities/tasks/messages/es.json +204 -0
  163. package/entities/tasks/migrations/001_tasks_table.sql +105 -0
  164. package/entities/tasks/migrations/002_task_metas.sql +85 -0
  165. package/entities/tasks/migrations/sample_data.json +77 -0
  166. package/entities/tasks/tasks.config.ts +79 -0
  167. package/entities/tasks/tasks.fields.ts +196 -0
  168. package/entities/tasks/tasks.service.ts +541 -0
  169. package/entities/tasks/tasks.types.ts +56 -0
  170. package/lib/hooks/useAiChat.ts +114 -0
  171. package/lib/hooks/useConversations.ts +376 -0
  172. package/lib/hooks/useOrchestratorChat.ts +122 -0
  173. package/lib/hooks/usePersistentChat.ts +315 -0
  174. package/lib/hooks/useStreamingChat.ts +127 -0
  175. package/lib/hooks/useTokenUsage.ts +63 -0
  176. package/lib/langchain/agents/customer-assistant.md +69 -0
  177. package/lib/langchain/agents/index.ts +61 -0
  178. package/lib/langchain/agents/orchestrator.md +59 -0
  179. package/lib/langchain/agents/page-assistant.md +85 -0
  180. package/lib/langchain/agents/single-agent.md +46 -0
  181. package/lib/langchain/agents/task-assistant.md +55 -0
  182. package/lib/langchain/config.ts +45 -0
  183. package/lib/langchain/handlers/customer-handler.ts +338 -0
  184. package/lib/langchain/handlers/page-handler.ts +232 -0
  185. package/lib/langchain/handlers/task-handler.ts +323 -0
  186. package/lib/langchain/langchain.config.ts +223 -0
  187. package/lib/langchain/observability.config.ts +30 -0
  188. package/lib/langchain/orchestrator.ts +562 -0
  189. package/lib/langchain/tools/customers.ts +176 -0
  190. package/lib/langchain/tools/index.ts +10 -0
  191. package/lib/langchain/tools/orchestrator.ts +92 -0
  192. package/lib/langchain/tools/pages.ts +289 -0
  193. package/lib/langchain/tools/tasks.ts +167 -0
  194. package/lib/scheduled-actions/billing.ts +149 -0
  195. package/lib/scheduled-actions/index.ts +170 -0
  196. package/lib/scheduled-actions/webhook.ts +231 -0
  197. package/lib/selectors.ts +197 -0
  198. package/messages/de/admin.json +219 -0
  199. package/messages/de/aiUsage.json +36 -0
  200. package/messages/de/buttons.json +19 -0
  201. package/messages/de/categories.json +35 -0
  202. package/messages/de/common.json +16 -0
  203. package/messages/de/dev.json +101 -0
  204. package/messages/de/docs.json +27 -0
  205. package/messages/de/entities.json +7 -0
  206. package/messages/de/features.json +119 -0
  207. package/messages/de/footer.json +22 -0
  208. package/messages/de/home.json +57 -0
  209. package/messages/de/index.ts +39 -0
  210. package/messages/de/mobileNav.json +13 -0
  211. package/messages/de/navigation.json +8 -0
  212. package/messages/de/observability.json +74 -0
  213. package/messages/de/posts.json +54 -0
  214. package/messages/de/pricing.json +102 -0
  215. package/messages/de/support.json +9 -0
  216. package/messages/de/teams.json +8 -0
  217. package/messages/en/admin.json +219 -0
  218. package/messages/en/aiUsage.json +36 -0
  219. package/messages/en/buttons.json +19 -0
  220. package/messages/en/categories.json +35 -0
  221. package/messages/en/common.json +16 -0
  222. package/messages/en/dev.json +106 -0
  223. package/messages/en/docs.json +27 -0
  224. package/messages/en/entities.json +7 -0
  225. package/messages/en/features.json +119 -0
  226. package/messages/en/footer.json +22 -0
  227. package/messages/en/home.json +57 -0
  228. package/messages/en/index.ts +39 -0
  229. package/messages/en/mobileNav.json +13 -0
  230. package/messages/en/navigation.json +8 -0
  231. package/messages/en/observability.json +74 -0
  232. package/messages/en/posts.json +54 -0
  233. package/messages/en/pricing.json +102 -0
  234. package/messages/en/support.json +9 -0
  235. package/messages/en/teams.json +8 -0
  236. package/messages/es/admin.json +219 -0
  237. package/messages/es/aiUsage.json +36 -0
  238. package/messages/es/buttons.json +19 -0
  239. package/messages/es/categories.json +35 -0
  240. package/messages/es/common.json +16 -0
  241. package/messages/es/dev.json +101 -0
  242. package/messages/es/docs.json +27 -0
  243. package/messages/es/entities.json +7 -0
  244. package/messages/es/features.json +119 -0
  245. package/messages/es/footer.json +22 -0
  246. package/messages/es/home.json +57 -0
  247. package/messages/es/index.ts +39 -0
  248. package/messages/es/mobileNav.json +13 -0
  249. package/messages/es/navigation.json +8 -0
  250. package/messages/es/observability.json +74 -0
  251. package/messages/es/posts.json +54 -0
  252. package/messages/es/pricing.json +102 -0
  253. package/messages/es/support.json +9 -0
  254. package/messages/es/teams.json +8 -0
  255. package/messages/fr/admin.json +219 -0
  256. package/messages/fr/aiUsage.json +36 -0
  257. package/messages/fr/buttons.json +19 -0
  258. package/messages/fr/categories.json +35 -0
  259. package/messages/fr/common.json +16 -0
  260. package/messages/fr/dev.json +101 -0
  261. package/messages/fr/docs.json +27 -0
  262. package/messages/fr/entities.json +7 -0
  263. package/messages/fr/features.json +119 -0
  264. package/messages/fr/footer.json +22 -0
  265. package/messages/fr/home.json +57 -0
  266. package/messages/fr/index.ts +39 -0
  267. package/messages/fr/mobileNav.json +13 -0
  268. package/messages/fr/navigation.json +8 -0
  269. package/messages/fr/observability.json +74 -0
  270. package/messages/fr/posts.json +54 -0
  271. package/messages/fr/pricing.json +102 -0
  272. package/messages/fr/support.json +9 -0
  273. package/messages/fr/teams.json +8 -0
  274. package/messages/it/admin.json +219 -0
  275. package/messages/it/aiUsage.json +36 -0
  276. package/messages/it/buttons.json +19 -0
  277. package/messages/it/categories.json +35 -0
  278. package/messages/it/common.json +16 -0
  279. package/messages/it/dev.json +101 -0
  280. package/messages/it/docs.json +27 -0
  281. package/messages/it/entities.json +7 -0
  282. package/messages/it/features.json +119 -0
  283. package/messages/it/footer.json +22 -0
  284. package/messages/it/home.json +57 -0
  285. package/messages/it/index.ts +39 -0
  286. package/messages/it/mobileNav.json +13 -0
  287. package/messages/it/navigation.json +8 -0
  288. package/messages/it/observability.json +74 -0
  289. package/messages/it/posts.json +54 -0
  290. package/messages/it/pricing.json +102 -0
  291. package/messages/it/support.json +9 -0
  292. package/messages/it/teams.json +8 -0
  293. package/messages/pt/admin.json +219 -0
  294. package/messages/pt/aiUsage.json +36 -0
  295. package/messages/pt/buttons.json +19 -0
  296. package/messages/pt/categories.json +35 -0
  297. package/messages/pt/common.json +16 -0
  298. package/messages/pt/dev.json +101 -0
  299. package/messages/pt/docs.json +27 -0
  300. package/messages/pt/entities.json +7 -0
  301. package/messages/pt/features.json +119 -0
  302. package/messages/pt/footer.json +22 -0
  303. package/messages/pt/home.json +57 -0
  304. package/messages/pt/index.ts +39 -0
  305. package/messages/pt/mobileNav.json +13 -0
  306. package/messages/pt/navigation.json +8 -0
  307. package/messages/pt/observability.json +74 -0
  308. package/messages/pt/posts.json +54 -0
  309. package/messages/pt/pricing.json +102 -0
  310. package/messages/pt/support.json +9 -0
  311. package/messages/pt/teams.json +8 -0
  312. package/migrations/089_add_editor_team_role.sql +39 -0
  313. package/migrations/090_demo_users_teams.sql +540 -0
  314. package/migrations/091_greek_teams_billing.sql +523 -0
  315. package/migrations/092_billing_sample_data.sql +774 -0
  316. package/migrations/093_pages_sample_data.sql +1158 -0
  317. package/migrations/094_posts_sample_data.sql +278 -0
  318. package/migrations/095_tasks_sample_data.sql +440 -0
  319. package/migrations/096_customers_sample_data.sql +358 -0
  320. package/migrations/097_scheduled_actions_sample_data.sql +111 -0
  321. package/package.json +22 -0
  322. package/public/docs/desktop-layout-example.png +0 -0
  323. package/styles/components.css +11 -0
  324. package/styles/globals.css +179 -0
  325. package/templates/(public)/blog/[slug]/page.tsx +65 -0
  326. package/templates/(public)/layout.tsx +25 -0
  327. package/templates/(public)/page.tsx +200 -0
  328. package/templates/(public)/support/page.tsx +321 -0
  329. package/templates/dashboard/(main)/agent-multi/page.tsx +63 -0
  330. package/templates/dashboard/(main)/agent-single/page.tsx +142 -0
  331. package/templates/dashboard/(main)/settings/ai-usage/page.tsx +157 -0
  332. package/templates/superadmin/ai-observability/[traceId]/page.tsx +27 -0
  333. package/templates/superadmin/ai-observability/page.tsx +17 -0
@@ -0,0 +1,562 @@
1
+ /**
2
+ * ============================================================================
3
+ * ORCHESTRATOR HANDLER
4
+ * ============================================================================
5
+ *
6
+ * Routes user requests to specialized agents based on intent analysis.
7
+ *
8
+ * TWO MODES:
9
+ * 1. Graph-based (new, efficient) - Uses LangGraph explicit state machine
10
+ * - 1-2 LLM calls instead of 50+
11
+ * - No recursion limit issues
12
+ * - Deterministic execution
13
+ *
14
+ * 2. ReAct-based (legacy) - Uses ReAct loops with sub-agents
15
+ * - More flexible but slower
16
+ * - Can hit recursion limits on complex queries
17
+ *
18
+ * Set LANGCHAIN_USE_GRAPH_ORCHESTRATOR=true to enable graph mode.
19
+ *
20
+ * Configuration is centralized in: langchain.config.ts
21
+ *
22
+ * ============================================================================
23
+ */
24
+
25
+ import { createAgent } from '@/plugins/langchain/lib/agent-factory'
26
+ import { invokeOrchestrator as invokeGraphOrchestrator } from '@/plugins/langchain/lib/graph'
27
+ import { memoryStore, createHumanMessage, createAIMessage } from '@/plugins/langchain/lib/memory-store'
28
+ import { compilePrompt, hasTemplateVariables } from '@/plugins/langchain/lib/prompt-renderer'
29
+ import { tracer } from '@/plugins/langchain/lib/tracer'
30
+ import type { AgentContext } from '@/plugins/langchain/types/langchain.types'
31
+ import type { ModelConfig } from '@/plugins/langchain/lib/graph/types'
32
+ import { loadSystemPrompt } from './agents'
33
+ import {
34
+ getAgentConfig,
35
+ getAgentModelConfig,
36
+ getAgentTools,
37
+ getAgentSessionConfig,
38
+ getAgentEnrichContext,
39
+ AGENTS,
40
+ observabilityConfig,
41
+ } from './langchain.config'
42
+ import type { RoutingResult, ClarificationResult } from './tools/orchestrator'
43
+ import { orchestratorConfig } from './config'
44
+
45
+ // ============================================================================
46
+ // INITIALIZATION
47
+ // ============================================================================
48
+
49
+ // Initialize tracer with theme's observability config
50
+ tracer.init(observabilityConfig.observability)
51
+
52
+ // Cache for compiled Handlebars templates (performance optimization)
53
+ const compiledTemplateCache = new Map<string, (context: AgentContext) => string>()
54
+
55
+ // Sub-agent types that can be routed to
56
+ type AgentType = 'task' | 'customer' | 'page'
57
+
58
+ // Mapping from routing type to agent name in config
59
+ const AGENT_NAME_MAP: Record<AgentType, string> = {
60
+ task: 'task-assistant',
61
+ customer: 'customer-assistant',
62
+ page: 'page-assistant',
63
+ }
64
+
65
+ // Message type from LangChain (simplified for orchestration)
66
+ interface AgentMessage {
67
+ _getType(): string
68
+ content: string | unknown
69
+ }
70
+
71
+ /**
72
+ * Context for orchestrator operations
73
+ */
74
+ export interface OrchestratorContext {
75
+ userId: string
76
+ teamId: string
77
+ sessionId: string
78
+ /** User display name for logging (e.g., "Carlos García") */
79
+ userName?: string
80
+ }
81
+
82
+ /**
83
+ * Response from the orchestrator
84
+ */
85
+ export interface OrchestratorResponse {
86
+ content: string
87
+ sessionId: string
88
+ agentUsed?: 'orchestrator' | AgentType
89
+ }
90
+
91
+ /**
92
+ * Format a clarification question for the user
93
+ */
94
+ function formatClarificationQuestion(result: ClarificationResult): string {
95
+ let response = result.question + '\n\n'
96
+ result.options.forEach((opt, i) => {
97
+ response += `${i + 1}. **${opt.label}**: ${opt.description}\n`
98
+ })
99
+ return response
100
+ }
101
+
102
+ /**
103
+ * Parse JSON content to determine routing
104
+ */
105
+ function parseRoutingDecision(content: string): RoutingResult | ClarificationResult | null {
106
+ try {
107
+ const parsed = JSON.parse(content)
108
+
109
+ // Check if it's a clarification request
110
+ if (parsed.action === 'clarify') {
111
+ return parsed as ClarificationResult
112
+ }
113
+
114
+ // Check if it's a routing decision
115
+ if (parsed.agent && ['task', 'customer', 'page'].includes(parsed.agent)) {
116
+ return parsed as RoutingResult
117
+ }
118
+
119
+ return null
120
+ } catch {
121
+ // Not a JSON response
122
+ return null
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Extract routing decision from the LATEST turn only
128
+ *
129
+ * IMPORTANT: We only look at messages from the current turn (after the last human message)
130
+ * to avoid picking up stale routing decisions from conversation history.
131
+ *
132
+ * @param messages - Full message array from agent
133
+ * @returns Routing decision if found in current turn, null otherwise
134
+ */
135
+ function extractRoutingFromMessages(messages: AgentMessage[]): RoutingResult | ClarificationResult | null {
136
+ // Find the index of the last human message (start of current turn)
137
+ let lastHumanIndex = -1
138
+ for (let i = messages.length - 1; i >= 0; i--) {
139
+ if (messages[i]._getType() === 'human') {
140
+ lastHumanIndex = i
141
+ break
142
+ }
143
+ }
144
+
145
+ // Only search for tool results AFTER the last human message (current turn)
146
+ for (let i = messages.length - 1; i > lastHumanIndex; i--) {
147
+ const msg = messages[i]
148
+ if (msg._getType() === 'tool') {
149
+ const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)
150
+ const decision = parseRoutingDecision(content)
151
+ if (decision) {
152
+ return decision
153
+ }
154
+ }
155
+ }
156
+ return null
157
+ }
158
+
159
+ /**
160
+ * Parse routing from AI text output (fallback for models that don't call tools properly)
161
+ */
162
+ function parseRoutingFromAIText(content: string): RoutingResult | null {
163
+ const routeMatch = content.match(/route_to_(task|customer|page)\s*\(\s*\{[^}]*"message"\s*:\s*"([^"]+)"/)
164
+ if (routeMatch) {
165
+ return {
166
+ agent: routeMatch[1] as AgentType,
167
+ message: routeMatch[2],
168
+ }
169
+ }
170
+ return null
171
+ }
172
+
173
+ /**
174
+ * Get system prompt for an agent
175
+ * Handles both inline prompts and file-based prompts
176
+ * Renders Handlebars templates with context data
177
+ * Uses compiled template cache for performance
178
+ */
179
+ function getSystemPromptForAgent(agentName: string, context?: AgentContext): string {
180
+ const agentConfig = getAgentConfig(agentName)
181
+ if (!agentConfig?.systemPrompt) {
182
+ throw new Error(`No system prompt configured for agent: ${agentName}`)
183
+ }
184
+
185
+ // Check cache first for compiled template
186
+ let compiledFn = compiledTemplateCache.get(agentName)
187
+
188
+ if (!compiledFn) {
189
+ let template: string
190
+
191
+ // If it's an inline prompt (contains newlines), use directly
192
+ if (agentConfig.systemPrompt.includes('\n')) {
193
+ template = agentConfig.systemPrompt
194
+ } else {
195
+ // Otherwise load from .md file
196
+ template = loadSystemPrompt(agentConfig.systemPrompt as any)
197
+ }
198
+
199
+ // Only compile if template has Handlebars syntax
200
+ if (hasTemplateVariables(template)) {
201
+ compiledFn = compilePrompt(template)
202
+ compiledTemplateCache.set(agentName, compiledFn)
203
+ } else {
204
+ // No template variables, return as-is
205
+ return template
206
+ }
207
+ }
208
+
209
+ // Render template with context if provided
210
+ if (context && compiledFn) {
211
+ return compiledFn(context)
212
+ }
213
+
214
+ // Fallback: return raw template if no context
215
+ const rawTemplate = agentConfig.systemPrompt.includes('\n')
216
+ ? agentConfig.systemPrompt
217
+ : loadSystemPrompt(agentConfig.systemPrompt as any)
218
+ return rawTemplate
219
+ }
220
+
221
+ /**
222
+ * Invoke a specialized sub-agent
223
+ *
224
+ * CONTEXT INHERITANCE: If parentContext is provided (from orchestrator),
225
+ * it will be merged with sub-agent's enriched data to avoid duplicate queries.
226
+ *
227
+ * @param agentType - The type of agent to invoke
228
+ * @param message - The message to process
229
+ * @param context - Base orchestrator context
230
+ * @param parentContext - Optional pre-enriched context from parent (orchestrator)
231
+ * @param parentId - Optional parent trace ID for observability linking
232
+ */
233
+ async function invokeSubAgent(
234
+ agentType: AgentType,
235
+ message: string,
236
+ context: OrchestratorContext,
237
+ parentContext?: AgentContext,
238
+ parentId?: string
239
+ ): Promise<OrchestratorResponse> {
240
+ const { userId, teamId, sessionId } = context
241
+ const agentName = AGENT_NAME_MAP[agentType]
242
+
243
+ // Start with parent context if available, otherwise build base
244
+ let agentContext: AgentContext = parentContext
245
+ ? { ...parentContext }
246
+ : { userId, teamId }
247
+
248
+ // Only enrich if not already enriched by parent OR if agent has specific enrichment
249
+ const enrichContext = getAgentEnrichContext(agentName)
250
+ if (enrichContext) {
251
+ // Check if already enriched by parent
252
+ const isAlreadyEnriched = parentContext?.__enriched === true
253
+
254
+ if (!isAlreadyEnriched) {
255
+ // First-time enrichment
256
+ agentContext = await enrichContext(agentContext)
257
+ agentContext.__enriched = true
258
+ } else {
259
+ // Parent already enriched - only add agent-specific data
260
+ // Merge parent context with any agent-specific additions
261
+ const agentSpecificContext = await enrichContext({ userId, teamId })
262
+ agentContext = {
263
+ ...agentContext,
264
+ ...agentSpecificContext,
265
+ // Keep the enriched flag and core fields
266
+ userId,
267
+ teamId,
268
+ __enriched: true,
269
+ }
270
+ }
271
+ }
272
+
273
+ // Get session config for memory customization
274
+ const sessionConfig = getAgentSessionConfig(agentName)
275
+
276
+ const agent = await createAgent({
277
+ sessionId: `${sessionId}-${agentType}`,
278
+ agentName,
279
+ systemPrompt: getSystemPromptForAgent(agentName, agentContext),
280
+ tools: getAgentTools(agentName, agentContext),
281
+ modelConfig: getAgentModelConfig(agentName),
282
+ context: agentContext,
283
+ sessionConfig,
284
+ parentId, // Link to orchestrator's trace for observability
285
+ })
286
+
287
+ const response = await agent.chat(message)
288
+
289
+ return {
290
+ content: response.content,
291
+ sessionId,
292
+ agentUsed: agentType,
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Process a message through the orchestrator
298
+ *
299
+ * The orchestrator:
300
+ * 1. Analyzes the user's request
301
+ * 2. Determines which agent should handle it (or asks for clarification)
302
+ * 3. Routes to the appropriate specialized agent
303
+ * 4. Returns the response
304
+ *
305
+ * @param message - The user's message
306
+ * @param context - User and session context
307
+ * @returns The response from the appropriate agent
308
+ */
309
+ export async function processWithOrchestrator(
310
+ message: string,
311
+ context: OrchestratorContext
312
+ ): Promise<OrchestratorResponse> {
313
+ const { userId, teamId, sessionId } = context
314
+
315
+ try {
316
+ // Build base context for orchestrator
317
+ let orchestratorContext: AgentContext = { userId, teamId }
318
+
319
+ // Enrich context with runtime data if enrichContext is defined
320
+ const enrichContext = getAgentEnrichContext('orchestrator')
321
+ if (enrichContext) {
322
+ orchestratorContext = await enrichContext(orchestratorContext)
323
+ // Mark as enriched to avoid duplicate enrichment in sub-agents
324
+ orchestratorContext.__enriched = true
325
+ }
326
+
327
+ // Get session config for memory customization
328
+ const sessionConfig = getAgentSessionConfig('orchestrator')
329
+
330
+ // Step 1: Create orchestrator agent to determine routing
331
+ const orchestratorAgent = await createAgent({
332
+ sessionId: `${sessionId}-orchestrator`,
333
+ agentName: 'orchestrator',
334
+ systemPrompt: getSystemPromptForAgent('orchestrator', orchestratorContext),
335
+ tools: getAgentTools('orchestrator', orchestratorContext),
336
+ modelConfig: getAgentModelConfig('orchestrator'),
337
+ context: orchestratorContext,
338
+ sessionConfig,
339
+ })
340
+
341
+ // Step 2: Get routing decision from orchestrator
342
+ const routingResponse = await orchestratorAgent.chat(message)
343
+ const orchestratorTraceId = routingResponse.traceId // Capture for parent-child linking
344
+
345
+ // Step 3: Extract routing decision from tool results in messages
346
+ let decision = extractRoutingFromMessages(routingResponse.messages || [])
347
+
348
+ // Fallback: If no tool was called, try to parse the AI's text output
349
+ if (!decision && typeof routingResponse.content === 'string') {
350
+ decision = parseRoutingFromAIText(routingResponse.content)
351
+ }
352
+
353
+ if (!decision) {
354
+ // Orchestrator responded directly (e.g., greeting, meta-question)
355
+ return {
356
+ content: routingResponse.content,
357
+ sessionId,
358
+ agentUsed: 'orchestrator',
359
+ }
360
+ }
361
+
362
+ // Step 4: Handle clarification requests
363
+ if ('action' in decision && decision.action === 'clarify') {
364
+ return {
365
+ content: formatClarificationQuestion(decision),
366
+ sessionId,
367
+ agentUsed: 'orchestrator',
368
+ }
369
+ }
370
+
371
+ // Step 5: Route to specialized agent with inherited context
372
+ if ('agent' in decision) {
373
+ return await invokeSubAgent(
374
+ decision.agent,
375
+ decision.message || message,
376
+ context,
377
+ orchestratorContext, // Pass enriched context to avoid duplicate queries
378
+ orchestratorTraceId // Pass trace ID for parent-child linking
379
+ )
380
+ }
381
+
382
+ // Fallback: return orchestrator's response
383
+ return {
384
+ content: routingResponse.content,
385
+ sessionId,
386
+ agentUsed: 'orchestrator',
387
+ }
388
+ } catch (error) {
389
+ throw new Error(
390
+ error instanceof Error ? error.message : 'Failed to process message'
391
+ )
392
+ }
393
+ }
394
+
395
+ // ============================================================================
396
+ // GRAPH-BASED ORCHESTRATOR (NEW - EFFICIENT)
397
+ // ============================================================================
398
+
399
+ /**
400
+ * Check if graph-based orchestrator is enabled
401
+ */
402
+ function isGraphOrchestratorEnabled(): boolean {
403
+ return process.env.LANGCHAIN_USE_GRAPH_ORCHESTRATOR === 'true'
404
+ }
405
+
406
+ /**
407
+ * Process a message using the graph-based orchestrator
408
+ *
409
+ * BENEFITS:
410
+ * - 1-2 LLM calls instead of 50+
411
+ * - No recursion limit issues
412
+ * - Deterministic execution
413
+ * - Parallel handler execution for multi-intent queries
414
+ *
415
+ * @param message - The user's message
416
+ * @param context - User and session context
417
+ * @returns The response
418
+ */
419
+ async function processWithGraphOrchestratorInternal(
420
+ message: string,
421
+ context: OrchestratorContext
422
+ ): Promise<OrchestratorResponse> {
423
+ const { userId, teamId, sessionId, userName } = context
424
+ const agentContext: AgentContext = { userId, teamId, userName }
425
+
426
+ // Start trace for observability
427
+ const traceContext = await tracer.startTrace(
428
+ { userId, teamId },
429
+ 'graph-orchestrator',
430
+ message,
431
+ {
432
+ sessionId,
433
+ agentType: 'graph-orchestrator',
434
+ metadata: {
435
+ orchestratorType: 'langgraph',
436
+ version: '1.0',
437
+ },
438
+ tags: ['graph', 'orchestrator'],
439
+ }
440
+ )
441
+
442
+ const startTime = Date.now()
443
+ let llmCalls = 0
444
+ let toolCalls = 0
445
+
446
+ try {
447
+ // Get conversation history from memory store
448
+ const conversationHistory = await memoryStore.getMessages(
449
+ `${sessionId}-orchestrator`,
450
+ agentContext
451
+ )
452
+
453
+ // Get model config for router
454
+ const orchestratorModelConfig = getAgentModelConfig('orchestrator')
455
+ const modelConfig: ModelConfig = {
456
+ provider: orchestratorModelConfig?.provider || 'openai',
457
+ model: orchestratorModelConfig?.model,
458
+ temperature: 0.1, // Low temperature for consistent routing
459
+ }
460
+
461
+ // Invoke the graph with orchestrator configuration (tools registered by theme)
462
+ const result = await invokeGraphOrchestrator(
463
+ message,
464
+ sessionId,
465
+ agentContext,
466
+ conversationHistory.slice(-5), // Last 5 messages for context
467
+ orchestratorConfig, // Pass config instead of individual handlers
468
+ {
469
+ traceId: traceContext?.traceId,
470
+ modelConfig,
471
+ }
472
+ )
473
+
474
+ // Count LLM calls based on what was executed
475
+ // Router always uses 1 LLM call
476
+ llmCalls = 1
477
+ // Combiner uses 1 LLM call if there were multiple intents or handlers
478
+ const handlersExecuted = result.completedHandlers?.length || 0
479
+ if (handlersExecuted > 1 || (handlersExecuted === 1 && result.intents?.length > 1)) {
480
+ llmCalls = 2
481
+ }
482
+ // Tool calls = number of handlers executed (task, customer, page operations)
483
+ toolCalls = handlersExecuted
484
+
485
+ // Save the interaction to memory
486
+ await memoryStore.addMessages(
487
+ `${sessionId}-orchestrator`,
488
+ [
489
+ createHumanMessage(message),
490
+ createAIMessage(result.finalResponse || 'No response generated'),
491
+ ],
492
+ agentContext
493
+ )
494
+
495
+ // End trace with success
496
+ if (traceContext) {
497
+ await tracer.endTrace(
498
+ { userId, teamId },
499
+ traceContext.traceId,
500
+ {
501
+ output: result.finalResponse || '',
502
+ llmCalls,
503
+ toolCalls,
504
+ metadata: {
505
+ durationMs: Date.now() - startTime,
506
+ intentsDetected: result.intents?.length || 0,
507
+ handlersExecuted: result.completedHandlers || [],
508
+ hadClarification: result.needsClarification || false,
509
+ },
510
+ }
511
+ )
512
+ }
513
+
514
+ return {
515
+ content: result.finalResponse || 'I was unable to process your request.',
516
+ sessionId,
517
+ agentUsed: 'orchestrator',
518
+ }
519
+ } catch (error) {
520
+ console.error('[GraphOrchestrator] Error:', error)
521
+
522
+ // End trace with error
523
+ if (traceContext) {
524
+ await tracer.endTrace(
525
+ { userId, teamId },
526
+ traceContext.traceId,
527
+ {
528
+ error: error instanceof Error ? error : new Error(String(error)),
529
+ llmCalls,
530
+ toolCalls,
531
+ metadata: {
532
+ durationMs: Date.now() - startTime,
533
+ },
534
+ }
535
+ )
536
+ }
537
+
538
+ throw new Error(
539
+ error instanceof Error ? error.message : 'Failed to process message with graph orchestrator'
540
+ )
541
+ }
542
+ }
543
+
544
+ /**
545
+ * Unified entry point - uses graph or ReAct based on feature flag
546
+ *
547
+ * @param message - The user's message
548
+ * @param context - User and session context
549
+ * @returns The response from the appropriate agent
550
+ */
551
+ export async function processMessage(
552
+ message: string,
553
+ context: OrchestratorContext
554
+ ): Promise<OrchestratorResponse> {
555
+ if (isGraphOrchestratorEnabled()) {
556
+ console.log('[Orchestrator] Using graph-based orchestrator')
557
+ return processWithGraphOrchestratorInternal(message, context)
558
+ }
559
+
560
+ // Fallback to legacy ReAct-based orchestrator
561
+ return processWithOrchestrator(message, context)
562
+ }