@aigentic/ruflo 3.7.0-alpha.69

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 (524) hide show
  1. package/README.md +410 -0
  2. package/bin/ruflo.js +57 -0
  3. package/package.json +98 -0
  4. package/src/chat-ui/Dockerfile +25 -0
  5. package/src/chat-ui/patch-mcp-url-safety.sh +28 -0
  6. package/src/chat-ui/static/chatui/icon-144x144.png +0 -0
  7. package/src/chat-ui/static/chatui/omni-welcome.gif +0 -0
  8. package/src/config/config.example.json +76 -0
  9. package/src/mcp-bridge/Dockerfile +45 -0
  10. package/src/mcp-bridge/index.js +1668 -0
  11. package/src/mcp-bridge/mcp-stdio-kernel.js +159 -0
  12. package/src/mcp-bridge/package.json +17 -0
  13. package/src/mcp-bridge/test-harness.js +470 -0
  14. package/src/nginx/Dockerfile +10 -0
  15. package/src/nginx/nginx.conf +67 -0
  16. package/src/nginx/static/favicon-dark.svg +4 -0
  17. package/src/nginx/static/favicon.svg +4 -0
  18. package/src/nginx/static/icon.svg +5 -0
  19. package/src/nginx/static/logo.svg +9 -0
  20. package/src/nginx/static/manifest.json +22 -0
  21. package/src/nginx/static/welcome.js +184 -0
  22. package/src/ruvocal/.claude/skills/add-model-descriptions/SKILL.md +73 -0
  23. package/src/ruvocal/.devcontainer/Dockerfile +9 -0
  24. package/src/ruvocal/.devcontainer/devcontainer.json +36 -0
  25. package/src/ruvocal/.dockerignore +17 -0
  26. package/src/ruvocal/.eslintignore +13 -0
  27. package/src/ruvocal/.eslintrc.cjs +45 -0
  28. package/src/ruvocal/.gcloudignore +18 -0
  29. package/src/ruvocal/.github/ISSUE_TEMPLATE/bug-report--chat-ui-.md +43 -0
  30. package/src/ruvocal/.github/ISSUE_TEMPLATE/config-support.md +9 -0
  31. package/src/ruvocal/.github/ISSUE_TEMPLATE/feature-request--chat-ui-.md +17 -0
  32. package/src/ruvocal/.github/ISSUE_TEMPLATE/huggingchat.md +11 -0
  33. package/src/ruvocal/.github/release.yml +16 -0
  34. package/src/ruvocal/.github/workflows/build-docs.yml +18 -0
  35. package/src/ruvocal/.github/workflows/build-image.yml +142 -0
  36. package/src/ruvocal/.github/workflows/build-pr-docs.yml +20 -0
  37. package/src/ruvocal/.github/workflows/deploy-dev.yml +63 -0
  38. package/src/ruvocal/.github/workflows/deploy-prod.yml +78 -0
  39. package/src/ruvocal/.github/workflows/lint-and-test.yml +84 -0
  40. package/src/ruvocal/.github/workflows/slugify.yaml +72 -0
  41. package/src/ruvocal/.github/workflows/trufflehog.yml +17 -0
  42. package/src/ruvocal/.github/workflows/upload-pr-documentation.yml +16 -0
  43. package/src/ruvocal/.husky/lint-stage-config.js +4 -0
  44. package/src/ruvocal/.husky/pre-commit +2 -0
  45. package/src/ruvocal/.prettierignore +14 -0
  46. package/src/ruvocal/.prettierrc +7 -0
  47. package/src/ruvocal/CLAUDE.md +126 -0
  48. package/src/ruvocal/Dockerfile +96 -0
  49. package/src/ruvocal/LICENSE +203 -0
  50. package/src/ruvocal/PRIVACY.md +41 -0
  51. package/src/ruvocal/README.md +164 -0
  52. package/src/ruvocal/chart/Chart.yaml +5 -0
  53. package/src/ruvocal/chart/env/dev.yaml +260 -0
  54. package/src/ruvocal/chart/env/prod.yaml +273 -0
  55. package/src/ruvocal/chart/templates/_helpers.tpl +22 -0
  56. package/src/ruvocal/chart/templates/config.yaml +10 -0
  57. package/src/ruvocal/chart/templates/deployment.yaml +81 -0
  58. package/src/ruvocal/chart/templates/hpa.yaml +45 -0
  59. package/src/ruvocal/chart/templates/infisical.yaml +24 -0
  60. package/src/ruvocal/chart/templates/ingress-internal.yaml +32 -0
  61. package/src/ruvocal/chart/templates/ingress.yaml +32 -0
  62. package/src/ruvocal/chart/templates/network-policy.yaml +36 -0
  63. package/src/ruvocal/chart/templates/service-account.yaml +13 -0
  64. package/src/ruvocal/chart/templates/service-monitor.yaml +17 -0
  65. package/src/ruvocal/chart/templates/service.yaml +21 -0
  66. package/src/ruvocal/chart/values.yaml +73 -0
  67. package/src/ruvocal/cloudbuild.yaml +68 -0
  68. package/src/ruvocal/config/branding.env.example +19 -0
  69. package/src/ruvocal/docker-compose.yml +21 -0
  70. package/src/ruvocal/docs/adr/ADR-029-HUGGINGFACE-CHAT-UI-CLOUD-RUN.md +1236 -0
  71. package/src/ruvocal/docs/adr/ADR-033-RUVECTOR-RUFLO-MCP-INTEGRATION.md +111 -0
  72. package/src/ruvocal/docs/adr/ADR-034-OPTIONAL-MCP-BACKENDS.md +117 -0
  73. package/src/ruvocal/docs/adr/ADR-035-MCP-TOOL-GROUPS.md +186 -0
  74. package/src/ruvocal/docs/adr/ADR-037-AUTOPILOT-CHAT-MODE.md +1500 -0
  75. package/src/ruvocal/docs/adr/ADR-038-RUVOCAL-FORK.md +286 -0
  76. package/src/ruvocal/docs/source/_toctree.yml +30 -0
  77. package/src/ruvocal/docs/source/configuration/common-issues.md +38 -0
  78. package/src/ruvocal/docs/source/configuration/llm-router.md +105 -0
  79. package/src/ruvocal/docs/source/configuration/mcp-tools.md +84 -0
  80. package/src/ruvocal/docs/source/configuration/metrics.md +9 -0
  81. package/src/ruvocal/docs/source/configuration/open-id.md +57 -0
  82. package/src/ruvocal/docs/source/configuration/overview.md +89 -0
  83. package/src/ruvocal/docs/source/configuration/theming.md +20 -0
  84. package/src/ruvocal/docs/source/developing/architecture.md +48 -0
  85. package/src/ruvocal/docs/source/index.md +53 -0
  86. package/src/ruvocal/docs/source/installation/docker.md +43 -0
  87. package/src/ruvocal/docs/source/installation/helm.md +43 -0
  88. package/src/ruvocal/docs/source/installation/local.md +62 -0
  89. package/src/ruvocal/entrypoint.sh +19 -0
  90. package/src/ruvocal/mcp-bridge/Dockerfile +45 -0
  91. package/src/ruvocal/mcp-bridge/cloudbuild.yaml +49 -0
  92. package/src/ruvocal/mcp-bridge/index.js +1878 -0
  93. package/src/ruvocal/mcp-bridge/mcp-stdio-kernel.js +159 -0
  94. package/src/ruvocal/mcp-bridge/package-lock.json +762 -0
  95. package/src/ruvocal/mcp-bridge/package.json +17 -0
  96. package/src/ruvocal/mcp-bridge/test-harness.js +470 -0
  97. package/src/ruvocal/models/add-your-models-here.txt +1 -0
  98. package/src/ruvocal/package-lock.json +11741 -0
  99. package/src/ruvocal/package.json +121 -0
  100. package/src/ruvocal/postcss.config.js +6 -0
  101. package/src/ruvocal/rvf.manifest.json +204 -0
  102. package/src/ruvocal/scripts/config.ts +64 -0
  103. package/src/ruvocal/scripts/generate-welcome.mjs +181 -0
  104. package/src/ruvocal/scripts/populate.ts +288 -0
  105. package/src/ruvocal/scripts/samples.txt +194 -0
  106. package/src/ruvocal/scripts/setups/vitest-setup-client.ts +0 -0
  107. package/src/ruvocal/scripts/setups/vitest-setup-server.ts +44 -0
  108. package/src/ruvocal/scripts/updateLocalEnv.ts +48 -0
  109. package/src/ruvocal/src/ambient.d.ts +7 -0
  110. package/src/ruvocal/src/app.d.ts +29 -0
  111. package/src/ruvocal/src/app.html +53 -0
  112. package/src/ruvocal/src/hooks.server.ts +32 -0
  113. package/src/ruvocal/src/hooks.ts +6 -0
  114. package/src/ruvocal/src/lib/APIClient.ts +148 -0
  115. package/src/ruvocal/src/lib/actions/clickOutside.ts +18 -0
  116. package/src/ruvocal/src/lib/actions/snapScrollToBottom.ts +346 -0
  117. package/src/ruvocal/src/lib/buildPrompt.ts +33 -0
  118. package/src/ruvocal/src/lib/components/AnnouncementBanner.svelte +20 -0
  119. package/src/ruvocal/src/lib/components/BackgroundGenerationPoller.svelte +168 -0
  120. package/src/ruvocal/src/lib/components/CodeBlock.svelte +73 -0
  121. package/src/ruvocal/src/lib/components/CopyToClipBoardBtn.svelte +92 -0
  122. package/src/ruvocal/src/lib/components/DeleteConversationModal.svelte +75 -0
  123. package/src/ruvocal/src/lib/components/EditConversationModal.svelte +100 -0
  124. package/src/ruvocal/src/lib/components/ExpandNavigation.svelte +22 -0
  125. package/src/ruvocal/src/lib/components/FoundationBackground.svelte +242 -0
  126. package/src/ruvocal/src/lib/components/HoverTooltip.svelte +44 -0
  127. package/src/ruvocal/src/lib/components/HtmlPreviewModal.svelte +143 -0
  128. package/src/ruvocal/src/lib/components/InfiniteScroll.svelte +50 -0
  129. package/src/ruvocal/src/lib/components/MobileNav.svelte +300 -0
  130. package/src/ruvocal/src/lib/components/Modal.svelte +115 -0
  131. package/src/ruvocal/src/lib/components/ModelCardMetadata.svelte +71 -0
  132. package/src/ruvocal/src/lib/components/NavConversationItem.svelte +151 -0
  133. package/src/ruvocal/src/lib/components/NavMenu.svelte +313 -0
  134. package/src/ruvocal/src/lib/components/Pagination.svelte +97 -0
  135. package/src/ruvocal/src/lib/components/PaginationArrow.svelte +27 -0
  136. package/src/ruvocal/src/lib/components/Portal.svelte +24 -0
  137. package/src/ruvocal/src/lib/components/RetryBtn.svelte +18 -0
  138. package/src/ruvocal/src/lib/components/RuFloUniverse.svelte +185 -0
  139. package/src/ruvocal/src/lib/components/RufloHelpModal.svelte +411 -0
  140. package/src/ruvocal/src/lib/components/ScrollToBottomBtn.svelte +47 -0
  141. package/src/ruvocal/src/lib/components/ScrollToPreviousBtn.svelte +77 -0
  142. package/src/ruvocal/src/lib/components/ShareConversationModal.svelte +182 -0
  143. package/src/ruvocal/src/lib/components/StopGeneratingBtn.svelte +69 -0
  144. package/src/ruvocal/src/lib/components/SubscribeModal.svelte +87 -0
  145. package/src/ruvocal/src/lib/components/Switch.svelte +36 -0
  146. package/src/ruvocal/src/lib/components/SystemPromptModal.svelte +44 -0
  147. package/src/ruvocal/src/lib/components/Toast.svelte +27 -0
  148. package/src/ruvocal/src/lib/components/Tooltip.svelte +30 -0
  149. package/src/ruvocal/src/lib/components/WelcomeModal.svelte +46 -0
  150. package/src/ruvocal/src/lib/components/chat/Alternatives.svelte +77 -0
  151. package/src/ruvocal/src/lib/components/chat/BlockWrapper.svelte +72 -0
  152. package/src/ruvocal/src/lib/components/chat/ChatInput.svelte +490 -0
  153. package/src/ruvocal/src/lib/components/chat/ChatIntroduction.svelte +123 -0
  154. package/src/ruvocal/src/lib/components/chat/ChatMessage.svelte +548 -0
  155. package/src/ruvocal/src/lib/components/chat/ChatWindow.svelte +1057 -0
  156. package/src/ruvocal/src/lib/components/chat/FileDropzone.svelte +92 -0
  157. package/src/ruvocal/src/lib/components/chat/ImageLightbox.svelte +66 -0
  158. package/src/ruvocal/src/lib/components/chat/MarkdownBlock.svelte +23 -0
  159. package/src/ruvocal/src/lib/components/chat/MarkdownRenderer.svelte +69 -0
  160. package/src/ruvocal/src/lib/components/chat/MarkdownRenderer.svelte.test.ts +58 -0
  161. package/src/ruvocal/src/lib/components/chat/MessageAvatar.svelte +103 -0
  162. package/src/ruvocal/src/lib/components/chat/ModelSwitch.svelte +64 -0
  163. package/src/ruvocal/src/lib/components/chat/OpenReasoningResults.svelte +81 -0
  164. package/src/ruvocal/src/lib/components/chat/TaskGroup.svelte +88 -0
  165. package/src/ruvocal/src/lib/components/chat/ToolUpdate.svelte +273 -0
  166. package/src/ruvocal/src/lib/components/chat/UploadedFile.svelte +253 -0
  167. package/src/ruvocal/src/lib/components/chat/UrlFetchModal.svelte +203 -0
  168. package/src/ruvocal/src/lib/components/chat/VoiceRecorder.svelte +214 -0
  169. package/src/ruvocal/src/lib/components/icons/IconBurger.svelte +20 -0
  170. package/src/ruvocal/src/lib/components/icons/IconCheap.svelte +20 -0
  171. package/src/ruvocal/src/lib/components/icons/IconChevron.svelte +24 -0
  172. package/src/ruvocal/src/lib/components/icons/IconDazzled.svelte +40 -0
  173. package/src/ruvocal/src/lib/components/icons/IconFast.svelte +20 -0
  174. package/src/ruvocal/src/lib/components/icons/IconLoading.svelte +22 -0
  175. package/src/ruvocal/src/lib/components/icons/IconMCP.svelte +28 -0
  176. package/src/ruvocal/src/lib/components/icons/IconMoon.svelte +21 -0
  177. package/src/ruvocal/src/lib/components/icons/IconNew.svelte +20 -0
  178. package/src/ruvocal/src/lib/components/icons/IconOmni.svelte +90 -0
  179. package/src/ruvocal/src/lib/components/icons/IconPaperclip.svelte +24 -0
  180. package/src/ruvocal/src/lib/components/icons/IconPro.svelte +37 -0
  181. package/src/ruvocal/src/lib/components/icons/IconShare.svelte +21 -0
  182. package/src/ruvocal/src/lib/components/icons/IconSun.svelte +93 -0
  183. package/src/ruvocal/src/lib/components/icons/Logo.svelte +68 -0
  184. package/src/ruvocal/src/lib/components/icons/LogoHuggingFaceBorderless.svelte +54 -0
  185. package/src/ruvocal/src/lib/components/mcp/AddServerForm.svelte +250 -0
  186. package/src/ruvocal/src/lib/components/mcp/MCPServerManager.svelte +185 -0
  187. package/src/ruvocal/src/lib/components/mcp/ServerCard.svelte +203 -0
  188. package/src/ruvocal/src/lib/components/players/AudioPlayer.svelte +82 -0
  189. package/src/ruvocal/src/lib/components/voice/AudioWaveform.svelte +96 -0
  190. package/src/ruvocal/src/lib/components/wasm/GalleryPanel.svelte +357 -0
  191. package/src/ruvocal/src/lib/constants/mcpExamples.ts +114 -0
  192. package/src/ruvocal/src/lib/constants/mime.ts +11 -0
  193. package/src/ruvocal/src/lib/constants/pagination.ts +1 -0
  194. package/src/ruvocal/src/lib/constants/publicSepToken.ts +1 -0
  195. package/src/ruvocal/src/lib/constants/routerExamples.ts +133 -0
  196. package/src/ruvocal/src/lib/constants/rvagentPresets.ts +206 -0
  197. package/src/ruvocal/src/lib/createShareLink.ts +27 -0
  198. package/src/ruvocal/src/lib/jobs/refresh-conversation-stats.ts +297 -0
  199. package/src/ruvocal/src/lib/migrations/lock.ts +56 -0
  200. package/src/ruvocal/src/lib/migrations/migrations.spec.ts +74 -0
  201. package/src/ruvocal/src/lib/migrations/migrations.ts +109 -0
  202. package/src/ruvocal/src/lib/migrations/routines/01-update-search-assistants.ts +50 -0
  203. package/src/ruvocal/src/lib/migrations/routines/02-update-assistants-models.ts +48 -0
  204. package/src/ruvocal/src/lib/migrations/routines/04-update-message-updates.ts +151 -0
  205. package/src/ruvocal/src/lib/migrations/routines/05-update-message-files.ts +56 -0
  206. package/src/ruvocal/src/lib/migrations/routines/06-trim-message-updates.ts +56 -0
  207. package/src/ruvocal/src/lib/migrations/routines/08-update-featured-to-review.ts +32 -0
  208. package/src/ruvocal/src/lib/migrations/routines/09-delete-empty-conversations.spec.ts +214 -0
  209. package/src/ruvocal/src/lib/migrations/routines/09-delete-empty-conversations.ts +88 -0
  210. package/src/ruvocal/src/lib/migrations/routines/10-update-reports-assistantid.ts +29 -0
  211. package/src/ruvocal/src/lib/migrations/routines/index.ts +15 -0
  212. package/src/ruvocal/src/lib/server/__tests__/conversation-stop-generating.spec.ts +103 -0
  213. package/src/ruvocal/src/lib/server/abortRegistry.ts +57 -0
  214. package/src/ruvocal/src/lib/server/abortedGenerations.ts +43 -0
  215. package/src/ruvocal/src/lib/server/adminToken.ts +62 -0
  216. package/src/ruvocal/src/lib/server/api/__tests__/conversations-id.spec.ts +296 -0
  217. package/src/ruvocal/src/lib/server/api/__tests__/conversations-message.spec.ts +216 -0
  218. package/src/ruvocal/src/lib/server/api/__tests__/conversations.spec.ts +235 -0
  219. package/src/ruvocal/src/lib/server/api/__tests__/misc.spec.ts +72 -0
  220. package/src/ruvocal/src/lib/server/api/__tests__/testHelpers.ts +86 -0
  221. package/src/ruvocal/src/lib/server/api/__tests__/user-reports.spec.ts +78 -0
  222. package/src/ruvocal/src/lib/server/api/__tests__/user.spec.ts +239 -0
  223. package/src/ruvocal/src/lib/server/api/types.ts +37 -0
  224. package/src/ruvocal/src/lib/server/api/utils/requireAuth.ts +22 -0
  225. package/src/ruvocal/src/lib/server/api/utils/resolveConversation.ts +69 -0
  226. package/src/ruvocal/src/lib/server/api/utils/resolveModel.ts +27 -0
  227. package/src/ruvocal/src/lib/server/api/utils/superjsonResponse.ts +15 -0
  228. package/src/ruvocal/src/lib/server/apiToken.ts +11 -0
  229. package/src/ruvocal/src/lib/server/auth.ts +554 -0
  230. package/src/ruvocal/src/lib/server/config.ts +187 -0
  231. package/src/ruvocal/src/lib/server/conversation.ts +83 -0
  232. package/src/ruvocal/src/lib/server/database/__tests__/rvf.spec.ts +709 -0
  233. package/src/ruvocal/src/lib/server/database/postgres.ts +700 -0
  234. package/src/ruvocal/src/lib/server/database/rvf.ts +1078 -0
  235. package/src/ruvocal/src/lib/server/database.ts +145 -0
  236. package/src/ruvocal/src/lib/server/endpoints/document.ts +68 -0
  237. package/src/ruvocal/src/lib/server/endpoints/endpoints.ts +43 -0
  238. package/src/ruvocal/src/lib/server/endpoints/images.ts +211 -0
  239. package/src/ruvocal/src/lib/server/endpoints/openai/endpointOai.ts +266 -0
  240. package/src/ruvocal/src/lib/server/endpoints/openai/openAIChatToTextGenerationStream.ts +212 -0
  241. package/src/ruvocal/src/lib/server/endpoints/openai/openAICompletionToTextGenerationStream.ts +32 -0
  242. package/src/ruvocal/src/lib/server/endpoints/preprocessMessages.ts +61 -0
  243. package/src/ruvocal/src/lib/server/exitHandler.ts +59 -0
  244. package/src/ruvocal/src/lib/server/files/downloadFile.ts +34 -0
  245. package/src/ruvocal/src/lib/server/files/uploadFile.ts +29 -0
  246. package/src/ruvocal/src/lib/server/findRepoRoot.ts +13 -0
  247. package/src/ruvocal/src/lib/server/fonts/Inter-Black.ttf +0 -0
  248. package/src/ruvocal/src/lib/server/fonts/Inter-Bold.ttf +0 -0
  249. package/src/ruvocal/src/lib/server/fonts/Inter-ExtraBold.ttf +0 -0
  250. package/src/ruvocal/src/lib/server/fonts/Inter-ExtraLight.ttf +0 -0
  251. package/src/ruvocal/src/lib/server/fonts/Inter-Light.ttf +0 -0
  252. package/src/ruvocal/src/lib/server/fonts/Inter-Medium.ttf +0 -0
  253. package/src/ruvocal/src/lib/server/fonts/Inter-Regular.ttf +0 -0
  254. package/src/ruvocal/src/lib/server/fonts/Inter-SemiBold.ttf +0 -0
  255. package/src/ruvocal/src/lib/server/fonts/Inter-Thin.ttf +0 -0
  256. package/src/ruvocal/src/lib/server/generateFromDefaultEndpoint.ts +46 -0
  257. package/src/ruvocal/src/lib/server/hooks/error.ts +37 -0
  258. package/src/ruvocal/src/lib/server/hooks/fetch.ts +22 -0
  259. package/src/ruvocal/src/lib/server/hooks/handle.ts +250 -0
  260. package/src/ruvocal/src/lib/server/hooks/init.ts +51 -0
  261. package/src/ruvocal/src/lib/server/isURLLocal.spec.ts +31 -0
  262. package/src/ruvocal/src/lib/server/isURLLocal.ts +74 -0
  263. package/src/ruvocal/src/lib/server/logger.ts +42 -0
  264. package/src/ruvocal/src/lib/server/mcp/clientPool.spec.ts +175 -0
  265. package/src/ruvocal/src/lib/server/mcp/clientPool.ts +0 -0
  266. package/src/ruvocal/src/lib/server/mcp/hf.ts +32 -0
  267. package/src/ruvocal/src/lib/server/mcp/httpClient.ts +122 -0
  268. package/src/ruvocal/src/lib/server/mcp/registry.ts +76 -0
  269. package/src/ruvocal/src/lib/server/mcp/tools.ts +196 -0
  270. package/src/ruvocal/src/lib/server/metrics.ts +255 -0
  271. package/src/ruvocal/src/lib/server/models.ts +518 -0
  272. package/src/ruvocal/src/lib/server/requestContext.ts +55 -0
  273. package/src/ruvocal/src/lib/server/router/arch.ts +230 -0
  274. package/src/ruvocal/src/lib/server/router/endpoint.ts +316 -0
  275. package/src/ruvocal/src/lib/server/router/multimodal.ts +28 -0
  276. package/src/ruvocal/src/lib/server/router/policy.ts +49 -0
  277. package/src/ruvocal/src/lib/server/router/toolsRoute.ts +51 -0
  278. package/src/ruvocal/src/lib/server/router/types.ts +21 -0
  279. package/src/ruvocal/src/lib/server/sendSlack.ts +23 -0
  280. package/src/ruvocal/src/lib/server/textGeneration/generate.ts +258 -0
  281. package/src/ruvocal/src/lib/server/textGeneration/index.ts +96 -0
  282. package/src/ruvocal/src/lib/server/textGeneration/mcp/fileRefs.ts +155 -0
  283. package/src/ruvocal/src/lib/server/textGeneration/mcp/routerResolution.ts +108 -0
  284. package/src/ruvocal/src/lib/server/textGeneration/mcp/runMcpFlow.ts +831 -0
  285. package/src/ruvocal/src/lib/server/textGeneration/mcp/toolInvocation.ts +349 -0
  286. package/src/ruvocal/src/lib/server/textGeneration/mcp/wasmTools.test.ts +633 -0
  287. package/src/ruvocal/src/lib/server/textGeneration/reasoning.ts +23 -0
  288. package/src/ruvocal/src/lib/server/textGeneration/title.ts +83 -0
  289. package/src/ruvocal/src/lib/server/textGeneration/types.ts +28 -0
  290. package/src/ruvocal/src/lib/server/textGeneration/utils/prepareFiles.ts +88 -0
  291. package/src/ruvocal/src/lib/server/textGeneration/utils/routing.ts +21 -0
  292. package/src/ruvocal/src/lib/server/textGeneration/utils/toolPrompt.ts +49 -0
  293. package/src/ruvocal/src/lib/server/urlSafety.ts +77 -0
  294. package/src/ruvocal/src/lib/server/usageLimits.ts +30 -0
  295. package/src/ruvocal/src/lib/stores/autopilotStore.svelte.ts +175 -0
  296. package/src/ruvocal/src/lib/stores/backgroundGenerations.svelte.ts +32 -0
  297. package/src/ruvocal/src/lib/stores/backgroundGenerations.ts +1 -0
  298. package/src/ruvocal/src/lib/stores/errors.ts +9 -0
  299. package/src/ruvocal/src/lib/stores/isAborted.ts +3 -0
  300. package/src/ruvocal/src/lib/stores/isPro.ts +4 -0
  301. package/src/ruvocal/src/lib/stores/loading.ts +3 -0
  302. package/src/ruvocal/src/lib/stores/mcpServers.ts +534 -0
  303. package/src/ruvocal/src/lib/stores/pendingChatInput.ts +3 -0
  304. package/src/ruvocal/src/lib/stores/pendingMessage.ts +9 -0
  305. package/src/ruvocal/src/lib/stores/settings.ts +182 -0
  306. package/src/ruvocal/src/lib/stores/shareModal.ts +13 -0
  307. package/src/ruvocal/src/lib/stores/titleUpdate.ts +8 -0
  308. package/src/ruvocal/src/lib/stores/wasmMcp.ts +472 -0
  309. package/src/ruvocal/src/lib/switchTheme.ts +124 -0
  310. package/src/ruvocal/src/lib/types/AbortedGeneration.ts +8 -0
  311. package/src/ruvocal/src/lib/types/Assistant.ts +31 -0
  312. package/src/ruvocal/src/lib/types/AssistantStats.ts +11 -0
  313. package/src/ruvocal/src/lib/types/ConfigKey.ts +4 -0
  314. package/src/ruvocal/src/lib/types/ConvSidebar.ts +9 -0
  315. package/src/ruvocal/src/lib/types/Conversation.ts +27 -0
  316. package/src/ruvocal/src/lib/types/ConversationStats.ts +13 -0
  317. package/src/ruvocal/src/lib/types/Message.ts +41 -0
  318. package/src/ruvocal/src/lib/types/MessageEvent.ts +10 -0
  319. package/src/ruvocal/src/lib/types/MessageUpdate.ts +139 -0
  320. package/src/ruvocal/src/lib/types/MigrationResult.ts +7 -0
  321. package/src/ruvocal/src/lib/types/Model.ts +23 -0
  322. package/src/ruvocal/src/lib/types/Report.ts +12 -0
  323. package/src/ruvocal/src/lib/types/Review.ts +6 -0
  324. package/src/ruvocal/src/lib/types/Semaphore.ts +19 -0
  325. package/src/ruvocal/src/lib/types/Session.ts +22 -0
  326. package/src/ruvocal/src/lib/types/Settings.ts +93 -0
  327. package/src/ruvocal/src/lib/types/SharedConversation.ts +9 -0
  328. package/src/ruvocal/src/lib/types/Template.ts +6 -0
  329. package/src/ruvocal/src/lib/types/Timestamps.ts +4 -0
  330. package/src/ruvocal/src/lib/types/TokenCache.ts +6 -0
  331. package/src/ruvocal/src/lib/types/Tool.ts +77 -0
  332. package/src/ruvocal/src/lib/types/UrlDependency.ts +5 -0
  333. package/src/ruvocal/src/lib/types/User.ts +14 -0
  334. package/src/ruvocal/src/lib/utils/PublicConfig.svelte.ts +75 -0
  335. package/src/ruvocal/src/lib/utils/auth.ts +17 -0
  336. package/src/ruvocal/src/lib/utils/chunk.ts +33 -0
  337. package/src/ruvocal/src/lib/utils/cookiesAreEnabled.ts +13 -0
  338. package/src/ruvocal/src/lib/utils/debounce.ts +17 -0
  339. package/src/ruvocal/src/lib/utils/deepestChild.ts +6 -0
  340. package/src/ruvocal/src/lib/utils/favicon.ts +21 -0
  341. package/src/ruvocal/src/lib/utils/fetchJSON.ts +23 -0
  342. package/src/ruvocal/src/lib/utils/file2base64.ts +14 -0
  343. package/src/ruvocal/src/lib/utils/formatUserCount.ts +37 -0
  344. package/src/ruvocal/src/lib/utils/generationState.spec.ts +75 -0
  345. package/src/ruvocal/src/lib/utils/generationState.ts +26 -0
  346. package/src/ruvocal/src/lib/utils/getHref.ts +41 -0
  347. package/src/ruvocal/src/lib/utils/getReturnFromGenerator.ts +7 -0
  348. package/src/ruvocal/src/lib/utils/haptics.ts +64 -0
  349. package/src/ruvocal/src/lib/utils/hashConv.ts +12 -0
  350. package/src/ruvocal/src/lib/utils/hf.ts +17 -0
  351. package/src/ruvocal/src/lib/utils/isDesktop.ts +7 -0
  352. package/src/ruvocal/src/lib/utils/isUrl.ts +8 -0
  353. package/src/ruvocal/src/lib/utils/isVirtualKeyboard.ts +16 -0
  354. package/src/ruvocal/src/lib/utils/loadAttachmentsFromUrls.ts +115 -0
  355. package/src/ruvocal/src/lib/utils/marked.spec.ts +96 -0
  356. package/src/ruvocal/src/lib/utils/marked.ts +531 -0
  357. package/src/ruvocal/src/lib/utils/mcpValidation.ts +147 -0
  358. package/src/ruvocal/src/lib/utils/mergeAsyncGenerators.ts +38 -0
  359. package/src/ruvocal/src/lib/utils/messageUpdates.spec.ts +262 -0
  360. package/src/ruvocal/src/lib/utils/messageUpdates.ts +324 -0
  361. package/src/ruvocal/src/lib/utils/mime.ts +56 -0
  362. package/src/ruvocal/src/lib/utils/models.ts +14 -0
  363. package/src/ruvocal/src/lib/utils/parseBlocks.ts +120 -0
  364. package/src/ruvocal/src/lib/utils/parseIncompleteMarkdown.ts +644 -0
  365. package/src/ruvocal/src/lib/utils/parseStringToList.ts +10 -0
  366. package/src/ruvocal/src/lib/utils/randomUuid.ts +14 -0
  367. package/src/ruvocal/src/lib/utils/searchTokens.ts +33 -0
  368. package/src/ruvocal/src/lib/utils/sha256.ts +7 -0
  369. package/src/ruvocal/src/lib/utils/stringifyError.ts +12 -0
  370. package/src/ruvocal/src/lib/utils/sum.ts +3 -0
  371. package/src/ruvocal/src/lib/utils/template.spec.ts +59 -0
  372. package/src/ruvocal/src/lib/utils/template.ts +53 -0
  373. package/src/ruvocal/src/lib/utils/timeout.ts +9 -0
  374. package/src/ruvocal/src/lib/utils/toolProgress.spec.ts +46 -0
  375. package/src/ruvocal/src/lib/utils/toolProgress.ts +11 -0
  376. package/src/ruvocal/src/lib/utils/tree/addChildren.spec.ts +102 -0
  377. package/src/ruvocal/src/lib/utils/tree/addChildren.ts +48 -0
  378. package/src/ruvocal/src/lib/utils/tree/addSibling.spec.ts +81 -0
  379. package/src/ruvocal/src/lib/utils/tree/addSibling.ts +41 -0
  380. package/src/ruvocal/src/lib/utils/tree/buildSubtree.spec.ts +110 -0
  381. package/src/ruvocal/src/lib/utils/tree/buildSubtree.ts +24 -0
  382. package/src/ruvocal/src/lib/utils/tree/convertLegacyConversation.spec.ts +31 -0
  383. package/src/ruvocal/src/lib/utils/tree/convertLegacyConversation.ts +36 -0
  384. package/src/ruvocal/src/lib/utils/tree/isMessageId.spec.ts +15 -0
  385. package/src/ruvocal/src/lib/utils/tree/isMessageId.ts +5 -0
  386. package/src/ruvocal/src/lib/utils/tree/tree.d.ts +14 -0
  387. package/src/ruvocal/src/lib/utils/tree/treeHelpers.spec.ts +167 -0
  388. package/src/ruvocal/src/lib/utils/updates.ts +39 -0
  389. package/src/ruvocal/src/lib/utils/urlParams.ts +13 -0
  390. package/src/ruvocal/src/lib/wasm/idb.ts +438 -0
  391. package/src/ruvocal/src/lib/wasm/index.ts +1213 -0
  392. package/src/ruvocal/src/lib/wasm/tests/wasm-capabilities.test.ts +565 -0
  393. package/src/ruvocal/src/lib/wasm/wasm.worker.ts +332 -0
  394. package/src/ruvocal/src/lib/wasm/workerClient.ts +166 -0
  395. package/src/ruvocal/src/lib/workers/autopilotWorker.ts +221 -0
  396. package/src/ruvocal/src/lib/workers/detailFetchWorker.ts +100 -0
  397. package/src/ruvocal/src/lib/workers/markdownWorker.ts +61 -0
  398. package/src/ruvocal/src/routes/+error.svelte +20 -0
  399. package/src/ruvocal/src/routes/+layout.svelte +324 -0
  400. package/src/ruvocal/src/routes/+layout.ts +91 -0
  401. package/src/ruvocal/src/routes/+page.svelte +168 -0
  402. package/src/ruvocal/src/routes/.well-known/oauth-cimd/+server.ts +37 -0
  403. package/src/ruvocal/src/routes/__debug/openai/+server.ts +21 -0
  404. package/src/ruvocal/src/routes/admin/export/+server.ts +159 -0
  405. package/src/ruvocal/src/routes/admin/stats/compute/+server.ts +16 -0
  406. package/src/ruvocal/src/routes/api/conversation/[id]/+server.ts +40 -0
  407. package/src/ruvocal/src/routes/api/conversation/[id]/message/[messageId]/+server.ts +42 -0
  408. package/src/ruvocal/src/routes/api/conversations/+server.ts +48 -0
  409. package/src/ruvocal/src/routes/api/fetch-url/+server.ts +147 -0
  410. package/src/ruvocal/src/routes/api/mcp/health/+server.ts +292 -0
  411. package/src/ruvocal/src/routes/api/mcp/servers/+server.ts +32 -0
  412. package/src/ruvocal/src/routes/api/models/+server.ts +25 -0
  413. package/src/ruvocal/src/routes/api/transcribe/+server.ts +104 -0
  414. package/src/ruvocal/src/routes/api/user/+server.ts +15 -0
  415. package/src/ruvocal/src/routes/api/user/validate-token/+server.ts +20 -0
  416. package/src/ruvocal/src/routes/api/v2/conversations/+server.ts +48 -0
  417. package/src/ruvocal/src/routes/api/v2/conversations/[id]/+server.ts +94 -0
  418. package/src/ruvocal/src/routes/api/v2/conversations/[id]/message/[messageId]/+server.ts +43 -0
  419. package/src/ruvocal/src/routes/api/v2/conversations/import-share/+server.ts +23 -0
  420. package/src/ruvocal/src/routes/api/v2/debug/config/+server.ts +16 -0
  421. package/src/ruvocal/src/routes/api/v2/debug/refresh/+server.ts +30 -0
  422. package/src/ruvocal/src/routes/api/v2/export/+server.ts +196 -0
  423. package/src/ruvocal/src/routes/api/v2/feature-flags/+server.ts +14 -0
  424. package/src/ruvocal/src/routes/api/v2/models/+server.ts +38 -0
  425. package/src/ruvocal/src/routes/api/v2/models/[namespace]/+server.ts +8 -0
  426. package/src/ruvocal/src/routes/api/v2/models/[namespace]/[model]/+server.ts +8 -0
  427. package/src/ruvocal/src/routes/api/v2/models/[namespace]/[model]/subscribe/+server.ts +28 -0
  428. package/src/ruvocal/src/routes/api/v2/models/[namespace]/subscribe/+server.ts +28 -0
  429. package/src/ruvocal/src/routes/api/v2/models/old/+server.ts +7 -0
  430. package/src/ruvocal/src/routes/api/v2/models/refresh/+server.ts +33 -0
  431. package/src/ruvocal/src/routes/api/v2/public-config/+server.ts +7 -0
  432. package/src/ruvocal/src/routes/api/v2/user/+server.ts +17 -0
  433. package/src/ruvocal/src/routes/api/v2/user/billing-orgs/+server.ts +73 -0
  434. package/src/ruvocal/src/routes/api/v2/user/reports/+server.ts +17 -0
  435. package/src/ruvocal/src/routes/api/v2/user/settings/+server.ts +110 -0
  436. package/src/ruvocal/src/routes/conversation/+server.ts +115 -0
  437. package/src/ruvocal/src/routes/conversation/[id]/+page.svelte +586 -0
  438. package/src/ruvocal/src/routes/conversation/[id]/+page.ts +60 -0
  439. package/src/ruvocal/src/routes/conversation/[id]/+server.ts +740 -0
  440. package/src/ruvocal/src/routes/conversation/[id]/message/[messageId]/prompt/+server.ts +66 -0
  441. package/src/ruvocal/src/routes/conversation/[id]/share/+server.ts +69 -0
  442. package/src/ruvocal/src/routes/conversation/[id]/stop-generating/+server.ts +35 -0
  443. package/src/ruvocal/src/routes/healthcheck/+server.ts +3 -0
  444. package/src/ruvocal/src/routes/login/+server.ts +5 -0
  445. package/src/ruvocal/src/routes/login/callback/+server.ts +103 -0
  446. package/src/ruvocal/src/routes/login/callback/updateUser.spec.ts +157 -0
  447. package/src/ruvocal/src/routes/login/callback/updateUser.ts +215 -0
  448. package/src/ruvocal/src/routes/logout/+server.ts +18 -0
  449. package/src/ruvocal/src/routes/metrics/+server.ts +18 -0
  450. package/src/ruvocal/src/routes/models/+page.svelte +233 -0
  451. package/src/ruvocal/src/routes/models/[...model]/+page.svelte +161 -0
  452. package/src/ruvocal/src/routes/models/[...model]/+page.ts +14 -0
  453. package/src/ruvocal/src/routes/models/[...model]/thumbnail.png/+server.ts +64 -0
  454. package/src/ruvocal/src/routes/models/[...model]/thumbnail.png/ModelThumbnail.svelte +28 -0
  455. package/src/ruvocal/src/routes/privacy/+page.svelte +11 -0
  456. package/src/ruvocal/src/routes/r/[id]/+page.ts +34 -0
  457. package/src/ruvocal/src/routes/settings/(nav)/+layout.svelte +282 -0
  458. package/src/ruvocal/src/routes/settings/(nav)/+layout.ts +1 -0
  459. package/src/ruvocal/src/routes/settings/(nav)/+page.svelte +0 -0
  460. package/src/ruvocal/src/routes/settings/(nav)/+server.ts +59 -0
  461. package/src/ruvocal/src/routes/settings/(nav)/[...model]/+page.svelte +464 -0
  462. package/src/ruvocal/src/routes/settings/(nav)/[...model]/+page.ts +14 -0
  463. package/src/ruvocal/src/routes/settings/(nav)/application/+page.svelte +362 -0
  464. package/src/ruvocal/src/routes/settings/+layout.svelte +40 -0
  465. package/src/ruvocal/src/styles/highlight-js.css +195 -0
  466. package/src/ruvocal/src/styles/main.css +144 -0
  467. package/src/ruvocal/static/chatui/apple-touch-icon.png +0 -0
  468. package/src/ruvocal/static/chatui/favicon-dark.svg +3 -0
  469. package/src/ruvocal/static/chatui/favicon-dev.svg +3 -0
  470. package/src/ruvocal/static/chatui/favicon.ico +0 -0
  471. package/src/ruvocal/static/chatui/favicon.svg +3 -0
  472. package/src/ruvocal/static/chatui/icon-128x128.png +0 -0
  473. package/src/ruvocal/static/chatui/icon-144x144.png +0 -0
  474. package/src/ruvocal/static/chatui/icon-192x192.png +0 -0
  475. package/src/ruvocal/static/chatui/icon-256x256.png +0 -0
  476. package/src/ruvocal/static/chatui/icon-36x36.png +0 -0
  477. package/src/ruvocal/static/chatui/icon-48x48.png +0 -0
  478. package/src/ruvocal/static/chatui/icon-512x512.png +0 -0
  479. package/src/ruvocal/static/chatui/icon-72x72.png +0 -0
  480. package/src/ruvocal/static/chatui/icon-96x96.png +0 -0
  481. package/src/ruvocal/static/chatui/icon.svg +3 -0
  482. package/src/ruvocal/static/chatui/logo.svg +7 -0
  483. package/src/ruvocal/static/chatui/manifest.json +54 -0
  484. package/src/ruvocal/static/chatui/omni-welcome.gif +0 -0
  485. package/src/ruvocal/static/chatui/omni-welcome.png +0 -0
  486. package/src/ruvocal/static/chatui/welcome.js +184 -0
  487. package/src/ruvocal/static/chatui/welcome.svg +1 -0
  488. package/src/ruvocal/static/huggingchat/apple-touch-icon.png +0 -0
  489. package/src/ruvocal/static/huggingchat/assistants-thumbnail.png +0 -0
  490. package/src/ruvocal/static/huggingchat/castle-example.jpg +0 -0
  491. package/src/ruvocal/static/huggingchat/favicon-dark.svg +4 -0
  492. package/src/ruvocal/static/huggingchat/favicon-dev.svg +4 -0
  493. package/src/ruvocal/static/huggingchat/favicon.ico +0 -0
  494. package/src/ruvocal/static/huggingchat/favicon.svg +4 -0
  495. package/src/ruvocal/static/huggingchat/fulltext-logo.svg +2 -0
  496. package/src/ruvocal/static/huggingchat/icon-128x128.png +0 -0
  497. package/src/ruvocal/static/huggingchat/icon-144x144.png +0 -0
  498. package/src/ruvocal/static/huggingchat/icon-192x192.png +0 -0
  499. package/src/ruvocal/static/huggingchat/icon-256x256.png +0 -0
  500. package/src/ruvocal/static/huggingchat/icon-36x36.png +0 -0
  501. package/src/ruvocal/static/huggingchat/icon-48x48.png +0 -0
  502. package/src/ruvocal/static/huggingchat/icon-512x512.png +0 -0
  503. package/src/ruvocal/static/huggingchat/icon-72x72.png +0 -0
  504. package/src/ruvocal/static/huggingchat/icon-96x96.png +0 -0
  505. package/src/ruvocal/static/huggingchat/icon.svg +4 -0
  506. package/src/ruvocal/static/huggingchat/logo.svg +4 -0
  507. package/src/ruvocal/static/huggingchat/manifest.json +54 -0
  508. package/src/ruvocal/static/huggingchat/omni-welcome.gif +0 -0
  509. package/src/ruvocal/static/huggingchat/routes.chat.json +226 -0
  510. package/src/ruvocal/static/huggingchat/thumbnail.png +0 -0
  511. package/src/ruvocal/static/huggingchat/tools-thumbnail.png +0 -0
  512. package/src/ruvocal/static/robots.txt +10 -0
  513. package/src/ruvocal/static/wasm/rvagent_wasm.js +1539 -0
  514. package/src/ruvocal/static/wasm/rvagent_wasm_bg.wasm +0 -0
  515. package/src/ruvocal/stub/@reflink/reflink/index.js +0 -0
  516. package/src/ruvocal/stub/@reflink/reflink/package.json +5 -0
  517. package/src/ruvocal/svelte.config.js +53 -0
  518. package/src/ruvocal/tailwind.config.cjs +30 -0
  519. package/src/ruvocal/tsconfig.json +19 -0
  520. package/src/ruvocal/vite.config.ts +87 -0
  521. package/src/scripts/deploy.sh +116 -0
  522. package/src/scripts/generate-config.js +245 -0
  523. package/src/scripts/generate-welcome.js +187 -0
  524. package/src/scripts/package-rvf.sh +116 -0
@@ -0,0 +1,1878 @@
1
+ import express from "express";
2
+ import { spawn } from "child_process";
3
+ import { randomUUID } from "crypto";
4
+
5
+ // =============================================================================
6
+ // CONFIGURATION
7
+ // =============================================================================
8
+
9
+ const CLOUD_FUNCTIONS = {
10
+ search: process.env.SEARCH_API_URL || null,
11
+ research: process.env.RESEARCH_API_URL || null,
12
+ };
13
+
14
+ const PORT = parseInt(process.env.PORT || "3001", 10);
15
+
16
+ // =============================================================================
17
+ // TOOL GROUPS — Enable/disable categories of tools independently
18
+ // =============================================================================
19
+ // Groups map tool name prefixes from backends to logical categories.
20
+ // Each group can be toggled via env var. The AI sees only enabled tools.
21
+
22
+ const TOOL_GROUPS = {
23
+ // --- Core (always on, built-in) ---
24
+ core: {
25
+ enabled: true, // cannot be disabled
26
+ description: "Search, research, and guidance tools",
27
+ source: "builtin",
28
+ },
29
+
30
+ // --- Intelligence (ruvector) ---
31
+ intelligence: {
32
+ enabled: process.env.MCP_GROUP_INTELLIGENCE !== "false",
33
+ description: "Self-learning intelligence — routing, memory, pattern training (ruvector)",
34
+ source: "ruvector",
35
+ prefixes: ["hooks_"],
36
+ },
37
+
38
+ // --- Agents & Orchestration (ruflo) ---
39
+ agents: {
40
+ enabled: process.env.MCP_GROUP_AGENTS !== "false",
41
+ description: "Agent lifecycle, swarm coordination, task management, workflows (ruflo)",
42
+ source: "ruflo",
43
+ prefixes: ["agent_", "swarm_", "task_", "session_", "hive-mind_", "workflow_", "coordination_"],
44
+ },
45
+
46
+ // --- Memory & Knowledge (ruflo) ---
47
+ memory: {
48
+ enabled: process.env.MCP_GROUP_MEMORY !== "false",
49
+ description: "Vector memory, AgentDB, embeddings, semantic search (ruflo)",
50
+ source: "ruflo",
51
+ prefixes: ["memory_", "agentdb_", "embeddings_"],
52
+ },
53
+
54
+ // --- Dev Tools (ruflo) ---
55
+ devtools: {
56
+ enabled: process.env.MCP_GROUP_DEVTOOLS !== "false",
57
+ description: "Hooks, code analysis, performance profiling, GitHub integration (ruflo)",
58
+ source: "ruflo",
59
+ prefixes: ["hooks_", "analyze_", "performance_", "github_", "terminal_", "config_", "system_", "progress_"],
60
+ },
61
+
62
+ // --- Security & Safety (ruflo) ---
63
+ security: {
64
+ enabled: process.env.MCP_GROUP_SECURITY === "true",
65
+ description: "AI defence, PII detection, claims management, pattern transfer (ruflo)",
66
+ source: "ruflo",
67
+ prefixes: ["aidefence_", "claims_", "transfer_"],
68
+ },
69
+
70
+ // --- Browser Automation (ruflo) ---
71
+ browser: {
72
+ enabled: process.env.MCP_GROUP_BROWSER === "true",
73
+ description: "Headless browser control — navigate, click, fill, screenshot (ruflo)",
74
+ source: "ruflo",
75
+ prefixes: ["browser_"],
76
+ },
77
+
78
+ // --- Neural & DAA (ruflo) ---
79
+ neural: {
80
+ enabled: process.env.MCP_GROUP_NEURAL === "true",
81
+ description: "Neural network training, DAA autonomous agents, cognitive patterns (ruflo)",
82
+ source: "ruflo",
83
+ prefixes: ["neural_", "daa_"],
84
+ },
85
+
86
+ // --- Agentic Flow (agentic-flow@alpha) ---
87
+ "agentic-flow": {
88
+ enabled: process.env.MCP_GROUP_AGENTIC_FLOW === "true",
89
+ description: "Execute 66+ specialized agents, batch code editing, AgentDB patterns (agentic-flow)",
90
+ source: "agentic-flow",
91
+ prefixes: ["agentic_flow_", "agent_booster_", "agentdb_"],
92
+ },
93
+
94
+ // --- Claude Code ---
95
+ "claude-code": {
96
+ enabled: process.env.MCP_GROUP_CLAUDE_CODE === "true",
97
+ description: "Anthropic Claude Code — file editing, bash execution, code analysis (requires ANTHROPIC_API_KEY)",
98
+ source: "claude",
99
+ },
100
+
101
+ // --- Gemini MCP ---
102
+ gemini: {
103
+ enabled: process.env.MCP_GROUP_GEMINI === "true",
104
+ description: "Google Gemini conversation context, multimodal capabilities (requires GOOGLE_API_KEY)",
105
+ source: "gemini-mcp",
106
+ },
107
+
108
+ // --- OpenAI Codex ---
109
+ codex: {
110
+ enabled: process.env.MCP_GROUP_CODEX === "true",
111
+ description: "OpenAI Codex coding agent — code generation and execution (requires OPENAI_API_KEY)",
112
+ source: "codex",
113
+ },
114
+ };
115
+
116
+ // =============================================================================
117
+ // STDIO MCP CLIENT — Connects to external MCP servers via child process
118
+ // =============================================================================
119
+
120
+ class StdioMcpClient {
121
+ constructor(name, command, args = []) {
122
+ this.name = name;
123
+ this.command = command;
124
+ this.args = args;
125
+ this.process = null;
126
+ this.tools = [];
127
+ this.ready = false;
128
+ this.pending = new Map();
129
+ this.buffer = "";
130
+ }
131
+
132
+ async start() {
133
+ return new Promise((resolve) => {
134
+ try {
135
+ this.process = spawn(this.command, this.args, {
136
+ stdio: ["pipe", "pipe", "pipe"],
137
+ env: { ...process.env },
138
+ });
139
+
140
+ this.process.stdout.on("data", (data) => this._onData(data.toString()));
141
+ this.process.stderr.on("data", (data) => {
142
+ const msg = data.toString().trim();
143
+ if (msg && !msg.startsWith("npm WARN")) console.error(`[${this.name}] ${msg}`);
144
+ });
145
+ this.process.on("error", (err) => {
146
+ console.error(`[${this.name}] spawn error:`, err.message);
147
+ this.ready = false;
148
+ resolve(false);
149
+ });
150
+ this.process.on("exit", (code) => {
151
+ console.log(`[${this.name}] exited with code ${code}`);
152
+ this.ready = false;
153
+ });
154
+
155
+ this._send("initialize", {
156
+ protocolVersion: "2024-11-05",
157
+ capabilities: {},
158
+ clientInfo: { name: "mcp-bridge", version: "2.0.0" },
159
+ }).then((result) => {
160
+ if (result && !result.error) {
161
+ this._notify("notifications/initialized", {});
162
+ return this._send("tools/list", {});
163
+ }
164
+ return null;
165
+ }).then((result) => {
166
+ if (result && result.tools) {
167
+ this.tools = result.tools.map(t => ({
168
+ ...t,
169
+ _originalName: t.name,
170
+ _backend: this.name,
171
+ }));
172
+ this.ready = true;
173
+ console.log(`[${this.name}] ${this.tools.length} tools loaded`);
174
+ }
175
+ resolve(this.ready);
176
+ }).catch((err) => {
177
+ console.error(`[${this.name}] init failed:`, err.message);
178
+ resolve(false);
179
+ });
180
+
181
+ setTimeout(() => { if (!this.ready) resolve(false); }, 60000);
182
+ } catch (err) {
183
+ console.error(`[${this.name}] failed to start:`, err.message);
184
+ resolve(false);
185
+ }
186
+ });
187
+ }
188
+
189
+ _onData(chunk) {
190
+ this.buffer += chunk;
191
+ const lines = this.buffer.split("\n");
192
+ this.buffer = lines.pop() || "";
193
+ for (const line of lines) {
194
+ const trimmed = line.trim();
195
+ if (!trimmed) continue;
196
+ try {
197
+ const msg = JSON.parse(trimmed);
198
+ if (msg.id && this.pending.has(msg.id)) {
199
+ const { resolve } = this.pending.get(msg.id);
200
+ this.pending.delete(msg.id);
201
+ resolve(msg.result || msg.error || {});
202
+ }
203
+ } catch { /* skip non-JSON */ }
204
+ }
205
+ }
206
+
207
+ _send(method, params) {
208
+ return new Promise((resolve, reject) => {
209
+ if (!this.process || this.process.killed) {
210
+ return reject(new Error(`${this.name} process not running`));
211
+ }
212
+ const id = randomUUID();
213
+ const msg = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
214
+ this.pending.set(id, { resolve, reject });
215
+ this.process.stdin.write(msg);
216
+ // initialize is the cold-start gate for backends like ruflo/ruvector
217
+ // which boot a full claude-flow / ruvector kernel — on Cloud Run with
218
+ // npx fetching artifacts it can take 45-60s. Other RPC methods are
219
+ // post-init and stay snappy.
220
+ const timeoutMs = method === "initialize" ? 120000 : 30000;
221
+ setTimeout(() => {
222
+ if (this.pending.has(id)) {
223
+ this.pending.delete(id);
224
+ reject(new Error(`${this.name} timeout for ${method}`));
225
+ }
226
+ }, timeoutMs);
227
+ });
228
+ }
229
+
230
+ _notify(method, params) {
231
+ if (!this.process || this.process.killed) return;
232
+ this.process.stdin.write(JSON.stringify({ jsonrpc: "2.0", method, params }) + "\n");
233
+ }
234
+
235
+ async callTool(originalName, args) {
236
+ if (!this.ready) return { error: `${this.name} backend not available` };
237
+ try {
238
+ return await this._send("tools/call", { name: originalName, arguments: args });
239
+ } catch (err) {
240
+ return { error: err.message };
241
+ }
242
+ }
243
+
244
+ stop() {
245
+ if (this.process && !this.process.killed) {
246
+ this.process.kill("SIGTERM");
247
+ this.process = null;
248
+ }
249
+ this.ready = false;
250
+ this.tools = [];
251
+ }
252
+ }
253
+
254
+ // =============================================================================
255
+ // BACKEND REGISTRY
256
+ // =============================================================================
257
+
258
+ const BACKEND_DEFS = [
259
+ { name: "ruvector", command: "npx", args: ["-y", "ruvector", "mcp", "start"], groups: ["intelligence"] },
260
+ { name: "ruflo", command: "npx", args: ["-y", "ruflo", "mcp", "start"], groups: ["agents", "memory", "devtools", "security", "browser", "neural"] },
261
+ { name: "agentic-flow", command: "npx", args: ["-y", "agentic-flow@alpha", "mcp", "start"], groups: ["agentic-flow"] },
262
+ { name: "claude", command: "claude", args: ["mcp", "serve"], groups: ["claude-code"] },
263
+ { name: "gemini-mcp", command: "npx", args: ["-y", "gemini-mcp-server"], groups: ["gemini"] },
264
+ { name: "codex", command: "npx", args: ["-y", "@openai/codex", "mcp-server"], groups: ["codex"] },
265
+ ];
266
+
267
+ const mcpBackends = new Map();
268
+ let allBackendTools = []; // all tools from all backends (pre-filter)
269
+
270
+ function isBackendNeeded(backendDef) {
271
+ return backendDef.groups.some(g => TOOL_GROUPS[g]?.enabled);
272
+ }
273
+
274
+ // Filter tools from a backend based on which groups are enabled
275
+ function filterToolsByGroups(tools, backendName) {
276
+ const enabledGroups = Object.entries(TOOL_GROUPS)
277
+ .filter(([, g]) => g.enabled && g.source === backendName);
278
+
279
+ if (enabledGroups.length === 0) return [];
280
+
281
+ // If any enabled group has no prefixes defined, include all tools from that backend
282
+ const hasWildcard = enabledGroups.some(([, g]) => !g.prefixes);
283
+ if (hasWildcard) return tools;
284
+
285
+ const enabledPrefixes = enabledGroups.flatMap(([, g]) => g.prefixes || []);
286
+ return tools.filter(t => enabledPrefixes.some(p => t._originalName.startsWith(p)));
287
+ }
288
+
289
+ // Get the final filtered tool list with namespaced names
290
+ function getActiveTools() {
291
+ const filtered = [];
292
+ for (const [backendName, client] of mcpBackends) {
293
+ const accepted = filterToolsByGroups(client.tools, backendName);
294
+ for (const t of accepted) {
295
+ filtered.push({ ...t, name: `${backendName}__${t._originalName}` });
296
+ }
297
+ }
298
+ return filtered;
299
+ }
300
+
301
+ async function initBackends() {
302
+ const needed = BACKEND_DEFS.filter(isBackendNeeded);
303
+ if (needed.length === 0) return;
304
+
305
+ console.log(`Starting ${needed.length} MCP backends: ${needed.map(b => b.name).join(", ")}`);
306
+
307
+ await Promise.allSettled(
308
+ needed.map(async (b) => {
309
+ const client = new StdioMcpClient(b.name, b.command, b.args);
310
+ const ok = await client.start();
311
+ if (ok) {
312
+ mcpBackends.set(b.name, client);
313
+ } else {
314
+ console.warn(`[${b.name}] failed to start`);
315
+ }
316
+ })
317
+ );
318
+
319
+ allBackendTools = getActiveTools();
320
+ console.log(`MCP backends: ${mcpBackends.size} active, ${allBackendTools.length} tools (filtered by groups)`);
321
+ }
322
+
323
+ process.on("SIGTERM", () => { for (const [, c] of mcpBackends) c.stop(); process.exit(0); });
324
+ process.on("SIGINT", () => { for (const [, c] of mcpBackends) c.stop(); process.exit(0); });
325
+
326
+ // =============================================================================
327
+ // BUILT-IN TOOLS (core group — always on)
328
+ // =============================================================================
329
+
330
+ const BUILTIN_TOOLS = [
331
+ {
332
+ name: "search",
333
+ description: "Search your knowledge base for relevant information.",
334
+ inputSchema: {
335
+ type: "object",
336
+ properties: {
337
+ query: { type: "string", description: "Natural language search query" },
338
+ limit: { type: "number", description: "Max results (default 5)", default: 5 },
339
+ },
340
+ required: ["query"],
341
+ },
342
+ },
343
+ {
344
+ name: "web_research",
345
+ description: "Search the web, fact-check claims, compare items, or conduct deep research. Actions: 'search' (quick), 'research' (deep report), 'compare' (side-by-side), 'fact_check' (verify claims), 'goap' (comprehensive multi-step research with verification).",
346
+ inputSchema: {
347
+ type: "object",
348
+ properties: {
349
+ action: { type: "string", enum: ["search", "research", "compare", "fact_check", "goap"], description: "Research action type", default: "search" },
350
+ query: { type: "string", description: "Search query or topic" },
351
+ items: { type: "array", items: { type: "string" }, description: "Items to compare (for 'compare')" },
352
+ claim: { type: "string", description: "Claim to verify (for 'fact_check')" },
353
+ verify: { type: "boolean", description: "Verify results in goap mode", default: true },
354
+ },
355
+ required: ["query"],
356
+ },
357
+ },
358
+ {
359
+ name: "guidance",
360
+ description: "Get instructions on how to use the available tool groups and services. Call this FIRST when unsure which tool to use, when a user asks 'what can you do?', or when you need to understand a specific tool group. Returns structured guidance for the AI on tool selection and usage patterns.",
361
+ inputSchema: {
362
+ type: "object",
363
+ properties: {
364
+ topic: {
365
+ type: "string",
366
+ enum: ["overview", "groups", "intelligence", "agents", "memory", "devtools", "security", "browser", "neural", "agentic-flow", "claude-code", "gemini", "codex", "tool"],
367
+ description: "What to get guidance on. Use 'overview' for capabilities summary, 'groups' to see all tool groups and their status, or a specific group name for detailed usage instructions.",
368
+ default: "overview",
369
+ },
370
+ tool_name: { type: "string", description: "Specific tool name to get detailed usage info (when topic='tool')" },
371
+ },
372
+ },
373
+ },
374
+ ];
375
+
376
+ // =============================================================================
377
+ // GUIDANCE ENGINE — AI-facing instruction system
378
+ // =============================================================================
379
+
380
+ function getGuidance(topic, toolName) {
381
+ const activeGroups = Object.entries(TOOL_GROUPS).filter(([, g]) => g.enabled);
382
+ const inactiveGroups = Object.entries(TOOL_GROUPS).filter(([, g]) => !g.enabled);
383
+ const externalTools = getActiveTools();
384
+
385
+ if (topic === "overview") {
386
+ return {
387
+ guidance: `# Tool Capabilities Overview
388
+
389
+ You have access to ${BUILTIN_TOOLS.length + externalTools.length} tools organized into ${activeGroups.length} active groups.
390
+
391
+ ## Active Groups
392
+ ${activeGroups.map(([name, g]) => {
393
+ const count = name === "core" ? BUILTIN_TOOLS.length : externalTools.filter(t => t.name.startsWith(g.source + "__")).length;
394
+ return `- **${name}** (${count} tools) — ${g.description}`;
395
+ }).join("\n")}
396
+
397
+ ## Inactive Groups (can be enabled)
398
+ ${inactiveGroups.map(([name, g]) => `- **${name}** — ${g.description}`).join("\n") || "None"}
399
+
400
+ ## Quick Decision Guide
401
+ - **Knowledge questions** → use \`search\` first, then \`web_research\` if needed
402
+ - **Current events / facts** → use \`web_research\` with action 'search' or 'goap'
403
+ - **Complex research** → use \`web_research\` with action 'goap' (multi-step pipeline)
404
+ - **"What can you do?"** → call \`guidance\` with topic 'groups'
405
+ - **Memory / recall** → use tools from the \`memory\` group
406
+ - **Agent orchestration** → use tools from the \`agents\` group
407
+ - **Code analysis / performance** → use tools from the \`devtools\` group
408
+
409
+ ## Rules
410
+ 1. Call tools FIRST, then present results conversationally
411
+ 2. Never show raw JSON — synthesize results naturally
412
+ 3. For complex questions, prefer GOAP pipeline (web_research action='goap')
413
+ 4. Call \`guidance\` with a specific group name to learn how to use that group's tools`,
414
+ topic: "overview",
415
+ };
416
+ }
417
+
418
+ if (topic === "groups") {
419
+ const groupList = Object.entries(TOOL_GROUPS).map(([name, g]) => {
420
+ const status = g.enabled ? "ACTIVE" : "INACTIVE";
421
+ const toolCount = name === "core" ? BUILTIN_TOOLS.length :
422
+ externalTools.filter(t => {
423
+ const backend = t._backend;
424
+ return g.source === backend && (!g.prefixes || g.prefixes.some(p => t._originalName.startsWith(p)));
425
+ }).length;
426
+ return `| ${name} | ${status} | ${toolCount} | ${g.description} |`;
427
+ });
428
+
429
+ return {
430
+ guidance: `# Tool Groups\n\n| Group | Status | Tools | Description |\n|-------|--------|-------|-------------|\n${groupList.join("\n")}`,
431
+ topic: "groups",
432
+ };
433
+ }
434
+
435
+ // Specific group guidance
436
+ const groupGuides = {
437
+ intelligence: `# Intelligence Group (ruvector)
438
+
439
+ Self-learning intelligence tools for routing and vector memory.
440
+
441
+ ## Key Tools
442
+ - **ruvector__hooks_route** — Route a task to the best agent type. Call with a task description.
443
+ - **ruvector__hooks_remember** — Store context/knowledge in vector memory for later recall.
444
+ - **ruvector__hooks_recall** — Search vector memory semantically. Good for finding past context.
445
+ - **ruvector__hooks_pretrain** — Bootstrap intelligence from a code repository.
446
+ - **ruvector__hooks_build_agents** — Generate optimized agent configurations.
447
+ - **ruvector__hooks_stats** — Get intelligence statistics and learning metrics.
448
+
449
+ ## When to Use
450
+ - Before starting complex tasks: route to find the best agent approach
451
+ - To store important findings for cross-session memory
452
+ - To recall previously stored patterns or solutions`,
453
+
454
+ agents: `# Agents & Orchestration Group (ruflo)
455
+
456
+ Multi-agent lifecycle management, swarm coordination, and task workflows.
457
+
458
+ ## Key Tools
459
+ - **ruflo__agent_spawn** — Create a new agent with specific capabilities
460
+ - **ruflo__agent_list** — List all active agents
461
+ - **ruflo__swarm_init** — Initialize a swarm with a topology (mesh, hierarchical, ring, star)
462
+ - **ruflo__task_create** — Create and assign tasks
463
+ - **ruflo__workflow_create** — Define multi-step workflows
464
+ - **ruflo__workflow_execute** — Execute a workflow
465
+ - **ruflo__hive-mind_init** — Start collective intelligence coordination
466
+ - **ruflo__coordination_orchestrate** — Multi-agent coordination
467
+
468
+ ## When to Use
469
+ - Complex tasks requiring multiple agents working together
470
+ - Pipeline workflows with sequential or parallel steps
471
+ - Distributed task management`,
472
+
473
+ memory: `# Memory & Knowledge Group (ruflo)
474
+
475
+ Vector storage, semantic search, AgentDB pattern learning, and embeddings.
476
+
477
+ ## Key Tools
478
+ - **ruflo__memory_store** — Store a value with vector embedding for semantic search
479
+ - **ruflo__memory_search** — Semantic search across stored memories (HNSW-indexed)
480
+ - **ruflo__memory_list** — List stored memory entries
481
+ - **ruflo__agentdb_pattern-store** — Store a reasoning pattern for learning
482
+ - **ruflo__agentdb_pattern-search** — Search for similar reasoning patterns
483
+ - **ruflo__agentdb_context-synthesize** — Synthesize context from stored memories
484
+ - **ruflo__embeddings_generate** — Generate vector embeddings for text
485
+ - **ruflo__embeddings_search** — Semantic similarity search
486
+
487
+ ## When to Use
488
+ - Persistent knowledge storage across sessions
489
+ - Finding similar past solutions or patterns
490
+ - Building semantic search over custom data`,
491
+
492
+ devtools: `# Dev Tools Group (ruflo)
493
+
494
+ Code analysis, performance profiling, GitHub integration, and terminal access.
495
+
496
+ ## Key Tools
497
+ - **ruflo__analyze_diff** — Analyze git diff for risk and change classification
498
+ - **ruflo__performance_benchmark** — Run performance benchmarks
499
+ - **ruflo__performance_bottleneck** — Detect performance bottlenecks
500
+ - **ruflo__github_repo_analyze** — Analyze a GitHub repository
501
+ - **ruflo__github_pr_manage** — Manage pull requests
502
+ - **ruflo__terminal_execute** — Execute commands in a terminal session
503
+
504
+ ## When to Use
505
+ - Code review and change risk assessment
506
+ - Performance analysis and optimization
507
+ - GitHub repository management`,
508
+
509
+ security: `# Security & Safety Group (ruflo)
510
+
511
+ AI defence, PII detection, and claims-based authorization.
512
+
513
+ ## Key Tools
514
+ - **ruflo__aidefence_scan** — Scan text for AI manipulation attempts
515
+ - **ruflo__aidefence_has_pii** — Check for PII (emails, phones, SSNs)
516
+ - **ruflo__aidefence_is_safe** — Quick safety check on input
517
+ - **ruflo__claims_claim** — Claim an issue for work
518
+ - **ruflo__claims_board** — Visual board of all claims
519
+
520
+ ## When to Use
521
+ - Input validation and safety checking
522
+ - PII detection before processing sensitive data
523
+ - Work item management across agents`,
524
+
525
+ browser: `# Browser Automation Group (ruflo)
526
+
527
+ Headless browser control for web interaction and testing.
528
+
529
+ ## Key Tools
530
+ - **ruflo__browser_open** — Navigate to a URL
531
+ - **ruflo__browser_click** — Click elements by reference
532
+ - **ruflo__browser_fill** — Fill form inputs
533
+ - **ruflo__browser_screenshot** — Capture page screenshots
534
+ - **ruflo__browser_snapshot** — Get accessibility tree for AI parsing
535
+ - **ruflo__browser_eval** — Execute JavaScript in page context
536
+
537
+ ## When to Use
538
+ - Web scraping and data extraction
539
+ - Automated testing (E2E)
540
+ - Form filling and web interaction`,
541
+
542
+ neural: `# Neural & DAA Group (ruflo)
543
+
544
+ Neural network operations and Decentralized Autonomous Agents.
545
+
546
+ ## Key Tools
547
+ - **ruflo__neural_train** — Train a neural model
548
+ - **ruflo__neural_predict** — Make predictions
549
+ - **ruflo__daa_agent_create** — Create an autonomous agent
550
+ - **ruflo__daa_workflow_create** — Create autonomous workflows
551
+ - **ruflo__daa_knowledge_share** — Share knowledge between agents
552
+
553
+ ## When to Use
554
+ - Pattern learning and prediction
555
+ - Autonomous agent workflows
556
+ - Knowledge transfer between agents`,
557
+
558
+ "agentic-flow": `# Agentic Flow Group (agentic-flow@alpha)
559
+
560
+ Execute 66+ specialized agents with boosted code editing and AgentDB.
561
+
562
+ ## Key Tools
563
+ - **agentic-flow__agentic_flow_agent** — Execute any of 66+ specialized agents
564
+ - **agentic-flow__agentic_flow_list_agents** — List available agent types
565
+ - **agentic-flow__agent_booster_edit_file** — 352x faster code editing
566
+ - **agentic-flow__agent_booster_batch_edit** — Multi-file refactoring
567
+ - **agentic-flow__agentdb_pattern_store** — Store reasoning patterns
568
+ - **agentic-flow__agentdb_pattern_search** — Search similar patterns
569
+
570
+ ## When to Use
571
+ - Complex code generation with specialized agents
572
+ - Batch code refactoring across files
573
+ - Agent selection when you need the right specialist`,
574
+
575
+ "claude-code": `# Claude Code Group
576
+
577
+ Anthropic Claude Code MCP server — full coding agent capabilities.
578
+
579
+ Requires: ANTHROPIC_API_KEY environment variable.
580
+
581
+ ## Capabilities
582
+ - File reading and editing
583
+ - Bash command execution
584
+ - Code analysis and generation
585
+ - Project exploration
586
+
587
+ ## When to Use
588
+ - When you need a second AI perspective on code
589
+ - Complex refactoring tasks
590
+ - Code review and analysis`,
591
+
592
+ gemini: `# Gemini MCP Group
593
+
594
+ Google Gemini with conversation context management.
595
+
596
+ Requires: GOOGLE_API_KEY environment variable (already set for Gemini models).
597
+
598
+ ## Capabilities
599
+ - Conversation context management
600
+ - Multimodal processing
601
+ - Google Search grounding
602
+
603
+ ## When to Use
604
+ - Extended context conversations
605
+ - Multimodal content processing`,
606
+
607
+ codex: `# Codex Group
608
+
609
+ OpenAI Codex coding agent.
610
+
611
+ Requires: OPENAI_API_KEY environment variable (already set for OpenAI models).
612
+
613
+ ## Capabilities
614
+ - Code generation and execution
615
+ - Code completion
616
+ - Code explanation
617
+
618
+ ## When to Use
619
+ - Code generation tasks
620
+ - Quick code completions
621
+ - Code explanation and documentation`,
622
+ };
623
+
624
+ if (topic === "tool" && toolName) {
625
+ const allTools = [...BUILTIN_TOOLS, ...externalTools];
626
+ const tool = allTools.find(t => t.name === toolName);
627
+ if (tool) {
628
+ const props = Object.entries(tool.inputSchema?.properties || {})
629
+ .map(([k, v]) => `- **${k}** (${v.type}) — ${v.description || ""}`)
630
+ .join("\n");
631
+ return { guidance: `# ${tool.name}\n\n${tool.description}\n\n## Parameters\n${props}`, topic: "tool" };
632
+ }
633
+ return { guidance: `Tool '${toolName}' not found. Call guidance with topic='groups' to see available tools.`, topic: "tool" };
634
+ }
635
+
636
+ if (groupGuides[topic]) {
637
+ const group = TOOL_GROUPS[topic];
638
+ if (!group?.enabled) {
639
+ return { guidance: `# ${topic} — INACTIVE\n\n${group?.description || ""}\n\nThis group is not enabled. Set the appropriate MCP_GROUP_* env var to "true" to activate it.`, topic };
640
+ }
641
+ return { guidance: groupGuides[topic], topic };
642
+ }
643
+
644
+ return { guidance: `Unknown topic '${topic}'. Use 'overview', 'groups', or a specific group name.`, topic };
645
+ }
646
+
647
+ // =============================================================================
648
+ // GEMINI GROUNDED SEARCH — Uses Gemini's built-in Google Search when no
649
+ // dedicated search Cloud Function is configured
650
+ // =============================================================================
651
+
652
+ async function geminiGroundedSearch(query, mode = "search") {
653
+ const apiKey = process.env.GOOGLE_API_KEY;
654
+ if (!apiKey) return { error: "No GOOGLE_API_KEY configured for search" };
655
+
656
+ // Empty/missing query produces a 400 from Gemini's generateContent endpoint.
657
+ // Return a structured error so the model can recover with a real query.
658
+ if (!query || typeof query !== "string" || !query.trim()) {
659
+ return {
660
+ error: "search requires a non-empty query string",
661
+ hint: "Call this tool again with { query: 'your search terms' }. For comparisons use { action: 'compare', query: 'item A vs item B' }; for fact-checking use { action: 'fact_check', claim: 'the claim text' }.",
662
+ };
663
+ }
664
+
665
+ const model = "gemini-2.5-flash";
666
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
667
+
668
+ const systemInstructions = {
669
+ search: `You are a helpful search assistant. Answer the query using Google Search results. Include key facts, sources, and relevant details. Be concise but thorough.`,
670
+ research: `You are a research analyst. Provide a comprehensive research report on the topic using Google Search results. Include: key findings, analysis, multiple perspectives, and source citations. Be thorough.`,
671
+ compare: `You are a comparison analyst. Compare the items using Google Search results. Create a structured comparison with pros/cons, key differences, and a recommendation.`,
672
+ fact_check: `You are a fact-checker. Verify the claim using Google Search results. Provide a verdict (TRUE/FALSE/PARTIALLY TRUE/UNVERIFIABLE), evidence, and sources.`,
673
+ };
674
+
675
+ const prompt = mode === "fact_check"
676
+ ? `Fact-check this claim: "${query}"`
677
+ : mode === "compare"
678
+ ? `Compare these items: ${query}`
679
+ : mode === "research"
680
+ ? `Research this topic thoroughly: ${query}`
681
+ : query;
682
+
683
+ try {
684
+ const resp = await fetch(url, {
685
+ method: "POST",
686
+ headers: { "Content-Type": "application/json" },
687
+ body: JSON.stringify({
688
+ system_instruction: { parts: [{ text: systemInstructions[mode] || systemInstructions.search }] },
689
+ contents: [{ parts: [{ text: prompt }] }],
690
+ tools: [{ google_search: {} }],
691
+ generationConfig: { temperature: 0.2 },
692
+ }),
693
+ signal: AbortSignal.timeout(30000),
694
+ });
695
+
696
+ if (!resp.ok) {
697
+ const errText = await resp.text();
698
+ console.error(`[gemini-search] API error ${resp.status}:`, errText.substring(0, 200));
699
+ return { error: `Search API error: ${resp.status}` };
700
+ }
701
+
702
+ const data = await resp.json();
703
+ const candidate = data.candidates?.[0];
704
+ const answer = candidate?.content?.parts?.map(p => p.text).filter(Boolean).join("\n") || "";
705
+
706
+ // Extract grounding metadata (sources)
707
+ const grounding = candidate?.groundingMetadata || {};
708
+ const chunks = grounding.groundingChunks || [];
709
+ const sources = chunks
710
+ .filter(c => c.web)
711
+ .map(c => ({ title: c.web.title || "", url: c.web.uri || "" }))
712
+ .filter(s => s.url);
713
+
714
+ // Extract search queries used
715
+ const searchQueries = (grounding.webSearchQueries || []);
716
+
717
+ return {
718
+ success: true,
719
+ answer,
720
+ sources: sources.slice(0, 8),
721
+ searchQueries,
722
+ groundingMetadata: { sources, searchQueries },
723
+ mode,
724
+ };
725
+ } catch (err) {
726
+ if (err.name === "AbortError" || err.name === "TimeoutError") return { error: "Search timed out" };
727
+ return { error: err.message };
728
+ }
729
+ }
730
+
731
+ // =============================================================================
732
+ // HELPER — Call a backend Cloud Function / API
733
+ // =============================================================================
734
+
735
+ async function callCloudFunction(url, payload, timeoutMs = 25000) {
736
+ const controller = new AbortController();
737
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
738
+ try {
739
+ const resp = await fetch(url, {
740
+ method: "POST",
741
+ headers: { "Content-Type": "application/json" },
742
+ body: JSON.stringify(payload),
743
+ signal: controller.signal,
744
+ });
745
+ return await resp.json();
746
+ } catch (err) {
747
+ if (err.name === "AbortError") return { error: "Request timed out", timeout: timeoutMs };
748
+ return { error: err.message };
749
+ } finally {
750
+ clearTimeout(timer);
751
+ }
752
+ }
753
+
754
+ // =============================================================================
755
+ // GOAP SEARCH PIPELINE
756
+ // =============================================================================
757
+
758
+ async function executeGoapSearch(query, args) {
759
+ const researchUrl = CLOUD_FUNCTIONS.research;
760
+ if (!researchUrl) return { error: "GOAP requires a 'research' URL in CLOUD_FUNCTIONS" };
761
+
762
+ const startTime = Date.now();
763
+
764
+ const composeResult = await callCloudFunction(researchUrl, {
765
+ action: "search",
766
+ query: `Break this question into 3-4 distinct search queries that would help answer it comprehensively. Return ONLY the queries, one per line:\n\n${query}`,
767
+ }, 30000);
768
+
769
+ let searchQueries = [query];
770
+ if (composeResult && !composeResult.error) {
771
+ const answer = composeResult.result?.answer || composeResult.answer || "";
772
+ const lines = answer.split("\n").map(l => l.replace(/^[\d\-\*\.\)]+\s*/, "").trim()).filter(l => l.length > 5 && l.length < 200);
773
+ if (lines.length >= 2) searchQueries = lines.slice(0, 4);
774
+ }
775
+
776
+ const searchResults = await Promise.all(
777
+ searchQueries.map(q => callCloudFunction(researchUrl, { action: "search", query: q }, 30000))
778
+ );
779
+
780
+ const allSources = [], allAnswers = [];
781
+ for (let i = 0; i < searchResults.length; i++) {
782
+ const r = searchResults[i];
783
+ if (r && !r.error && r.success !== false) {
784
+ const answer = r.result?.answer || r.answer || "";
785
+ if (answer) allAnswers.push({ query: searchQueries[i], answer });
786
+ const gm = r.result?.groundingMetadata || r.groundingMetadata || {};
787
+ if (gm.sources) allSources.push(...gm.sources);
788
+ }
789
+ }
790
+
791
+ const uniqueSources = [];
792
+ const seenUrls = new Set();
793
+ for (const src of allSources) {
794
+ const url = src.url || src.uri || "";
795
+ if (url && !seenUrls.has(url)) { seenUrls.add(url); uniqueSources.push(src); }
796
+ }
797
+
798
+ const synthesisInput = allAnswers.map(a => `## ${a.query}\n${a.answer}`).join("\n\n");
799
+ const synthesisResult = await callCloudFunction(researchUrl, {
800
+ action: "research",
801
+ topic: `Synthesize these findings into a comprehensive answer to: "${query}"\n\nFindings:\n${synthesisInput}`,
802
+ }, 60000);
803
+
804
+ const synthesizedAnswer = synthesisResult?.result?.answer || synthesisResult?.answer || synthesisInput;
805
+ const synthGm = synthesisResult?.result?.groundingMetadata || {};
806
+ if (synthGm.sources) {
807
+ for (const src of synthGm.sources) {
808
+ const url = src.url || src.uri || "";
809
+ if (url && !seenUrls.has(url)) { seenUrls.add(url); uniqueSources.push(src); }
810
+ }
811
+ }
812
+
813
+ let verification = { verified: true, confidence: "high" };
814
+ if (args.verify !== false && synthesizedAnswer.length > 100) {
815
+ const vr = await callCloudFunction(researchUrl, {
816
+ action: "fact_check", claim: synthesizedAnswer.substring(0, 500),
817
+ }, 30000);
818
+ if (vr && !vr.error && vr.result) {
819
+ verification = {
820
+ verified: vr.result.verdict !== "FALSE",
821
+ verdict: vr.result.verdict,
822
+ confidence: vr.result.confidence || "medium",
823
+ details: vr.result.analysis,
824
+ };
825
+ }
826
+ }
827
+
828
+ return {
829
+ answer: synthesizedAnswer, pipeline: "goap",
830
+ steps: { queries_composed: searchQueries.length, searches_executed: searchResults.filter(r => !r?.error).length, sources_found: uniqueSources.length, verification },
831
+ sources: uniqueSources.slice(0, 10), searchQueries, duration_ms: Date.now() - startTime,
832
+ };
833
+ }
834
+
835
+ // =============================================================================
836
+ // GOAP SEARCH PIPELINE — Gemini fallback (no Cloud Function needed)
837
+ // =============================================================================
838
+
839
+ async function executeGoapSearchGemini(query, args) {
840
+ const startTime = Date.now();
841
+
842
+ // Step 1: Decompose into sub-queries
843
+ const decompose = await geminiGroundedSearch(
844
+ `Break this question into 3-4 distinct search queries that would help answer it comprehensively. Return ONLY the queries, one per line:\n\n${query}`,
845
+ "search"
846
+ );
847
+
848
+ let searchQueries = [query];
849
+ if (decompose?.answer) {
850
+ const lines = decompose.answer.split("\n")
851
+ .map(l => l.replace(/^[\d\-\*\.\)]+\s*/, "").trim())
852
+ .filter(l => l.length > 5 && l.length < 200);
853
+ if (lines.length >= 2) searchQueries = lines.slice(0, 4);
854
+ }
855
+
856
+ // Step 2: Parallel searches
857
+ const searchResults = await Promise.all(
858
+ searchQueries.map(q => geminiGroundedSearch(q, "search"))
859
+ );
860
+
861
+ const allSources = [], allAnswers = [];
862
+ for (let i = 0; i < searchResults.length; i++) {
863
+ const r = searchResults[i];
864
+ if (r && !r.error && r.answer) {
865
+ allAnswers.push({ query: searchQueries[i], answer: r.answer });
866
+ if (r.sources) allSources.push(...r.sources);
867
+ }
868
+ }
869
+
870
+ // Dedupe sources
871
+ const seenUrls = new Set();
872
+ const uniqueSources = allSources.filter(s => {
873
+ if (seenUrls.has(s.url)) return false;
874
+ seenUrls.add(s.url);
875
+ return true;
876
+ });
877
+
878
+ // Step 3: Synthesize
879
+ const synthesisInput = allAnswers.map(a => `## ${a.query}\n${a.answer}`).join("\n\n");
880
+ const synthesis = await geminiGroundedSearch(
881
+ `Synthesize these findings into a comprehensive answer to: "${query}"\n\nFindings:\n${synthesisInput}`,
882
+ "research"
883
+ );
884
+
885
+ const finalAnswer = synthesis?.answer || synthesisInput;
886
+ if (synthesis?.sources) {
887
+ for (const s of synthesis.sources) {
888
+ if (!seenUrls.has(s.url)) { seenUrls.add(s.url); uniqueSources.push(s); }
889
+ }
890
+ }
891
+
892
+ // Step 4: Verify if requested
893
+ let verification = { verified: true, confidence: "high" };
894
+ if (args.verify !== false && finalAnswer.length > 100) {
895
+ const vr = await geminiGroundedSearch(finalAnswer.substring(0, 500), "fact_check");
896
+ if (vr && !vr.error) {
897
+ verification = { verified: true, confidence: "medium", details: vr.answer };
898
+ }
899
+ }
900
+
901
+ return {
902
+ success: true,
903
+ answer: finalAnswer,
904
+ pipeline: "goap-gemini",
905
+ steps: {
906
+ queries_composed: searchQueries.length,
907
+ searches_executed: searchResults.filter(r => !r?.error).length,
908
+ sources_found: uniqueSources.length,
909
+ verification,
910
+ },
911
+ sources: uniqueSources.slice(0, 10),
912
+ searchQueries,
913
+ duration_ms: Date.now() - startTime,
914
+ };
915
+ }
916
+
917
+ // =============================================================================
918
+ // TOOL EXECUTOR
919
+ // =============================================================================
920
+
921
+ async function executeTool(name, args) {
922
+ switch (name) {
923
+ case "search": {
924
+ if (CLOUD_FUNCTIONS.search) {
925
+ return callCloudFunction(CLOUD_FUNCTIONS.search, { query: args.query, limit: args.limit || 5 });
926
+ }
927
+ // Fallback: use Gemini grounded search
928
+ return geminiGroundedSearch(args.query, "search");
929
+ }
930
+
931
+ case "web_research": {
932
+ const action = args.action || "search";
933
+ if (action === "goap") {
934
+ // GOAP needs research endpoint — fall back to multi-search via Gemini
935
+ if (CLOUD_FUNCTIONS.research) return executeGoapSearch(args.query, args);
936
+ return executeGoapSearchGemini(args.query, args);
937
+ }
938
+ if (CLOUD_FUNCTIONS.research) {
939
+ const payload = { action };
940
+ if (action === "search") payload.query = args.query;
941
+ else if (action === "research") payload.topic = args.query;
942
+ else if (action === "compare") payload.items = args.items;
943
+ else if (action === "fact_check") payload.claim = args.claim || args.query;
944
+ return callCloudFunction(CLOUD_FUNCTIONS.research, payload, 60000);
945
+ }
946
+ // Fallback: Gemini grounded search
947
+ const mode = action === "fact_check" ? "fact_check" : action === "compare" ? "compare" : action === "research" ? "research" : "search";
948
+ const query = action === "compare" ? (args.items || []).join(" vs ") : (args.claim || args.query);
949
+ return geminiGroundedSearch(query, mode);
950
+ }
951
+
952
+ case "guidance":
953
+ return getGuidance(args.topic || "overview", args.tool_name);
954
+
955
+ default: {
956
+ // Route to external MCP backend
957
+ const activeTools = getActiveTools();
958
+ const extTool = activeTools.find(t => t.name === name);
959
+ if (extTool) {
960
+ const backend = mcpBackends.get(extTool._backend);
961
+ if (backend) return backend.callTool(extTool._originalName, args);
962
+ return { error: `Backend ${extTool._backend} not available` };
963
+ }
964
+ return { error: `Unknown tool: ${name}. Call 'guidance' with topic='groups' to see available tools.` };
965
+ }
966
+ }
967
+ }
968
+
969
+ // =============================================================================
970
+ // PER-GROUP TOOL HELPERS
971
+ // =============================================================================
972
+
973
+ // Get tools for a specific group only
974
+ function getToolsForGroup(groupName) {
975
+ const group = TOOL_GROUPS[groupName];
976
+ if (!group || !group.enabled) return [];
977
+ if (groupName === "core") return BUILTIN_TOOLS;
978
+
979
+ const allActive = getActiveTools();
980
+ if (!group.prefixes) {
981
+ // No prefix filter — return all tools from this backend
982
+ return allActive.filter(t => t._backend === group.source);
983
+ }
984
+ return allActive.filter(t =>
985
+ t._backend === group.source && group.prefixes.some(p => t._originalName.startsWith(p))
986
+ );
987
+ }
988
+
989
+ // Group display names for the Chat UI
990
+ const GROUP_DISPLAY_NAMES = {
991
+ core: "Core Tools",
992
+ intelligence: "Intelligence & Learning",
993
+ agents: "Agents & Orchestration",
994
+ memory: "Memory & Knowledge",
995
+ devtools: "Dev Tools & Analysis",
996
+ security: "Security & Safety",
997
+ browser: "Browser Automation",
998
+ neural: "Neural & DAA",
999
+ "agentic-flow": "Agentic Flow",
1000
+ "claude-code": "Claude Code",
1001
+ gemini: "Gemini",
1002
+ codex: "Codex",
1003
+ };
1004
+
1005
+ // =============================================================================
1006
+ // MCP SERVER — Multiple endpoints per group
1007
+ // =============================================================================
1008
+
1009
+ const app = express();
1010
+ app.use(express.json({ limit: "10mb" }));
1011
+
1012
+ // ---------- CORS middleware ----------
1013
+ app.use((req, res, next) => {
1014
+ res.setHeader("Access-Control-Allow-Origin", "*");
1015
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
1016
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
1017
+ if (req.method === "OPTIONS") return res.sendStatus(204);
1018
+ next();
1019
+ });
1020
+
1021
+ // ---------- Shared MCP handler ----------
1022
+ function createMcpHandler(groupName) {
1023
+ return async (req, res) => {
1024
+ const { method, id, params } = req.body;
1025
+ try {
1026
+ switch (method) {
1027
+ case "initialize":
1028
+ return res.json({
1029
+ jsonrpc: "2.0", id,
1030
+ result: {
1031
+ protocolVersion: "2024-11-05",
1032
+ capabilities: { tools: {} },
1033
+ serverInfo: { name: `mcp-bridge/${groupName}`, version: "2.0.0" },
1034
+ },
1035
+ });
1036
+ case "tools/list": {
1037
+ const tools = getToolsForGroup(groupName);
1038
+ return res.json({ jsonrpc: "2.0", id, result: { tools } });
1039
+ }
1040
+ case "tools/call": {
1041
+ const { name, arguments: toolArgs } = params;
1042
+ const result = await executeTool(name, toolArgs || {});
1043
+ return res.json({
1044
+ jsonrpc: "2.0", id,
1045
+ result: {
1046
+ content: [{ type: "text", text: typeof result === "string" ? result : JSON.stringify(result, null, 2) }],
1047
+ },
1048
+ });
1049
+ }
1050
+ case "notifications/initialized":
1051
+ return res.json({ jsonrpc: "2.0", id, result: {} });
1052
+ default:
1053
+ return res.json({ jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } });
1054
+ }
1055
+ } catch (err) {
1056
+ console.error(`MCP error [${groupName}/${method}]:`, err);
1057
+ return res.json({ jsonrpc: "2.0", id, error: { code: -32603, message: err.message } });
1058
+ }
1059
+ };
1060
+ }
1061
+
1062
+ function createMcpSseHandler(groupName) {
1063
+ return (req, res) => {
1064
+ res.setHeader("Content-Type", "text/event-stream");
1065
+ res.setHeader("Cache-Control", "no-cache");
1066
+ res.setHeader("Connection", "keep-alive");
1067
+ res.write(`data: ${JSON.stringify({ type: "endpoint", url: `/mcp/${groupName}` })}\n\n`);
1068
+ };
1069
+ }
1070
+
1071
+ // ---------- Register per-group endpoints ----------
1072
+ for (const groupName of Object.keys(TOOL_GROUPS)) {
1073
+ app.post(`/mcp/${groupName}`, createMcpHandler(groupName));
1074
+ app.get(`/mcp/${groupName}`, createMcpSseHandler(groupName));
1075
+ }
1076
+
1077
+ // ---------- Catch-all /mcp — serves ALL enabled tools (backwards-compatible) ----------
1078
+ app.post("/mcp", async (req, res) => {
1079
+ const { method, id, params } = req.body;
1080
+ try {
1081
+ switch (method) {
1082
+ case "initialize":
1083
+ return res.json({
1084
+ jsonrpc: "2.0", id,
1085
+ result: {
1086
+ protocolVersion: "2024-11-05",
1087
+ capabilities: { tools: {} },
1088
+ serverInfo: { name: "mcp-bridge", version: "2.0.0" },
1089
+ },
1090
+ });
1091
+ case "tools/list": {
1092
+ const activeTools = getActiveTools();
1093
+ return res.json({ jsonrpc: "2.0", id, result: { tools: [...BUILTIN_TOOLS, ...activeTools] } });
1094
+ }
1095
+ case "tools/call": {
1096
+ const { name, arguments: toolArgs } = params;
1097
+ const result = await executeTool(name, toolArgs || {});
1098
+ return res.json({
1099
+ jsonrpc: "2.0", id,
1100
+ result: {
1101
+ content: [{ type: "text", text: typeof result === "string" ? result : JSON.stringify(result, null, 2) }],
1102
+ },
1103
+ });
1104
+ }
1105
+ case "notifications/initialized":
1106
+ return res.json({ jsonrpc: "2.0", id, result: {} });
1107
+ default:
1108
+ return res.json({ jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } });
1109
+ }
1110
+ } catch (err) {
1111
+ console.error(`MCP error [${method}]:`, err);
1112
+ return res.json({ jsonrpc: "2.0", id, error: { code: -32603, message: err.message } });
1113
+ }
1114
+ });
1115
+
1116
+ app.get("/mcp", (req, res) => {
1117
+ res.setHeader("Content-Type", "text/event-stream");
1118
+ res.setHeader("Cache-Control", "no-cache");
1119
+ res.setHeader("Connection", "keep-alive");
1120
+ res.write(`data: ${JSON.stringify({ type: "endpoint", url: "/mcp" })}\n\n`);
1121
+ });
1122
+
1123
+ // ---------- GET /mcp-servers — returns MCP_SERVERS JSON for Chat UI config ----------
1124
+ app.get("/mcp-servers", (_, res) => {
1125
+ const servers = [];
1126
+ for (const [name, group] of Object.entries(TOOL_GROUPS)) {
1127
+ if (!group.enabled) continue;
1128
+ const tools = getToolsForGroup(name);
1129
+ if (tools.length === 0) continue;
1130
+ servers.push({
1131
+ name: GROUP_DISPLAY_NAMES[name] || name,
1132
+ url: `/mcp/${name}`,
1133
+ tools: tools.length,
1134
+ group: name,
1135
+ });
1136
+ }
1137
+ res.json(servers);
1138
+ });
1139
+
1140
+ // =============================================================================
1141
+ // CHAT COMPLETIONS PROXY
1142
+ // =============================================================================
1143
+
1144
+ const PROVIDER_ROUTES = {
1145
+ openai: { baseURL: "https://api.openai.com/v1/chat/completions", getKey: () => process.env.OPENAI_API_KEY },
1146
+ gemini: { baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", getKey: () => process.env.GOOGLE_API_KEY },
1147
+ openrouter: { baseURL: "https://openrouter.ai/api/v1/chat/completions", getKey: () => process.env.OPENROUTER_API_KEY },
1148
+ };
1149
+
1150
+ function resolveProvider(model) {
1151
+ if (typeof model === "string") {
1152
+ if (model.startsWith("gemini-")) return "gemini";
1153
+ if (model.includes("/")) return "openrouter";
1154
+ }
1155
+ return "openai";
1156
+ }
1157
+
1158
+ // =============================================================================
1159
+ // SYSTEM PROMPT — Injected server-side into every chat completion request
1160
+ // =============================================================================
1161
+ // This comprehensive prompt teaches the AI how to use all 200+ MCP tools
1162
+ // across 5 groups. It is injected as the first system message, ensuring
1163
+ // consistent behavior regardless of what preprompt the Chat UI sends.
1164
+
1165
+ function buildSystemPrompt() {
1166
+ // Build dynamic group status
1167
+ const enabledGroups = Object.entries(TOOL_GROUPS)
1168
+ .filter(([, g]) => g.enabled)
1169
+ .map(([name]) => name);
1170
+
1171
+ return `You are an intelligent AI assistant with powerful tools organized into ${enabledGroups.length} active groups.
1172
+
1173
+ # CRITICAL RULES
1174
+ 1. Use tools proactively — NEVER guess answers, ALWAYS search first
1175
+ 2. For ANY factual question, current event, or research request → call \`web_research\` IMMEDIATELY
1176
+ 3. NEVER say "I don't have access to real-time information" — you DO via web_research
1177
+ 4. NEVER say "I can't search the web" — you CAN via web_research
1178
+ 5. Call tools FIRST, present results conversationally AFTER
1179
+ 6. When multiple tools could help, call them ALL in parallel
1180
+
1181
+ # Tool Groups
1182
+
1183
+ Tools prefixed with backend name (e.g., \`ruflo__agent_spawn\`). Always use the full prefixed name.
1184
+
1185
+ ## Group 1: Core Tools (always on)
1186
+
1187
+ - **search** — Search your knowledge base (documents, workflows, how-tos).
1188
+ Use for: internal knowledge, company docs, past conversations.
1189
+ \`{"query": "how to process a claim"}\`
1190
+
1191
+ - **web_research** — Search the internet via Google. This is your primary research tool.
1192
+ ALWAYS use this for: current events, facts, comparisons, any external knowledge.
1193
+ Actions:
1194
+ - \`search\` — Quick web search. Default, fast. \`{"action": "search", "query": "latest news on X"}\`
1195
+ - \`research\` — Deep report with synthesis. \`{"action": "research", "query": "comprehensive analysis of X"}\`
1196
+ - \`compare\` — Side-by-side comparison. \`{"action": "compare", "query": "X", "items": ["A", "B"]}\`
1197
+ - \`fact_check\` — Verify a claim. \`{"action": "fact_check", "query": "claim to verify"}\`
1198
+ - \`goap\` — Multi-step pipeline: decomposes → parallel searches → synthesizes → verifies. BEST for important questions.
1199
+ \`{"action": "goap", "query": "complex question requiring thorough research"}\`
1200
+
1201
+ - **guidance** — Get help on tool groups or specific tool usage.
1202
+ Topics: \`overview\`, \`groups\`, \`agents\`, \`memory\`, \`intelligence\`, \`devtools\`
1203
+ \`{"topic": "overview"}\` or \`{"topic": "tool", "tool_name": "ruflo__agent_spawn"}\`
1204
+
1205
+ ## Group 2: Intelligence & Learning (ruvector)
1206
+ Pattern learning, routing, code analysis, and trajectory tracking. ${TOOL_GROUPS.intelligence.enabled ? "ACTIVE" : "DISABLED"}
1207
+
1208
+ ### Essential Intelligence Tools:
1209
+ - **ruvector__hooks_route** — Route a task to the optimal agent type. Call this FIRST for complex tasks.
1210
+ \`{"task": "describe what needs to be done", "context": ["relevant info"]}\`
1211
+ Returns ranked agent recommendations with confidence scores.
1212
+ - **ruvector__hooks_remember** — Store a key-value pair in persistent memory for cross-session recall.
1213
+ \`{"key": "pattern-name", "value": "what to remember", "namespace": "patterns"}\`
1214
+ - **ruvector__hooks_recall** — Retrieve a previously stored memory by key.
1215
+ - **ruvector__hooks_suggest_context** — Get contextual suggestions based on current work.
1216
+ - **ruvector__hooks_swarm_recommend** — Get swarm topology recommendation for a task type.
1217
+ - **ruvector__hooks_capabilities** — List all intelligence system capabilities.
1218
+
1219
+ ### Code Analysis:
1220
+ - **ruvector__hooks_ast_analyze** — Analyze code structure (AST) of a file.
1221
+ - **ruvector__hooks_ast_complexity** — Get complexity metrics for code.
1222
+ - **ruvector__hooks_security_scan** — Scan code for security vulnerabilities.
1223
+ - **ruvector__hooks_diff_analyze** — Analyze a code diff for risk and impact.
1224
+ - **ruvector__hooks_diff_similar** — Find similar past diffs/changes.
1225
+
1226
+ ### Trajectory Learning (for multi-step tasks):
1227
+ - **ruvector__hooks_trajectory_begin** — Start tracking a multi-step task for learning.
1228
+ - **ruvector__hooks_trajectory_step** — Record a step in the current trajectory.
1229
+ - **ruvector__hooks_trajectory_end** — End trajectory, triggering pattern extraction.
1230
+
1231
+ ### Memory & Compression:
1232
+ - **ruvector__hooks_compress** — Compress/summarize long text for efficient storage.
1233
+ - **ruvector__hooks_rag_context** — Get RAG context for a query from stored knowledge.
1234
+ - **ruvector__hooks_learn** — Force the system to learn from provided examples.
1235
+ - **ruvector__hooks_batch_learn** — Learn from multiple examples at once.
1236
+ - **ruvector__hooks_stats** — View learning statistics and metrics.
1237
+ - **ruvector__hooks_doctor** — Run diagnostics on the intelligence system.
1238
+
1239
+ ## Group 3: Agents & Orchestration (ruflo)
1240
+ Spawn agents, coordinate swarms, manage tasks and workflows. ${TOOL_GROUPS.agents.enabled ? "ACTIVE" : "DISABLED"}
1241
+
1242
+ ### Agent Lifecycle:
1243
+ - **ruflo__agent_spawn** — Create a new specialized agent.
1244
+ \`{"type": "coder|researcher|tester|reviewer|architect|security", "name": "optional-name"}\`
1245
+ Agent types and when to use them:
1246
+ - \`coder\` — Write code, implement features, fix bugs
1247
+ - \`researcher\` — Find information, analyze documentation, investigate
1248
+ - \`tester\` — Write tests, run test suites, validate behavior
1249
+ - \`reviewer\` — Review code quality, security, best practices
1250
+ - \`architect\` — Design systems, plan architectures, evaluate trade-offs
1251
+ - \`security\` — Audit security, find vulnerabilities, recommend fixes
1252
+ - **ruflo__agent_status** — Check an agent's current state. \`{"agentId": "agent-xxx"}\`
1253
+ - **ruflo__agent_list** — List all active agents with their states.
1254
+ - **ruflo__agent_terminate** — Stop an agent. \`{"agentId": "agent-xxx"}\`
1255
+ - **ruflo__agent_health** — Health check across all agents.
1256
+ - **ruflo__agent_pool** — View the agent pool and available capacity.
1257
+
1258
+ ### Swarm Coordination:
1259
+ - **ruflo__swarm_init** — Initialize a multi-agent swarm.
1260
+ \`{"topology": "hierarchical|mesh|ring|star", "maxAgents": 8, "strategy": "balanced|specialized|adaptive"}\`
1261
+ - \`hierarchical\` — Coordinator + workers, best for structured tasks (anti-drift)
1262
+ - \`mesh\` — Peer-to-peer, best for collaborative work
1263
+ - \`ring\` — Sequential pipeline, best for ordered processing
1264
+ - \`star\` — Central hub, best for fan-out parallel work
1265
+ - **ruflo__swarm_status** — Get swarm health, topology, and agent states.
1266
+ - **ruflo__swarm_health** — Detailed health metrics for the swarm.
1267
+ - **ruflo__swarm_shutdown** — Tear down a swarm and all its agents.
1268
+
1269
+ ### Task Management:
1270
+ - **ruflo__task_create** — Create a tracked task.
1271
+ \`{"description": "what needs to be done", "priority": "low|normal|high|critical"}\`
1272
+ - **ruflo__task_status** — Check task progress. \`{"taskId": "task-xxx"}\`
1273
+ - **ruflo__task_list** — List all tasks with their statuses.
1274
+ - **ruflo__task_complete** — Mark a task as done. \`{"taskId": "task-xxx"}\`
1275
+ - **ruflo__task_update** — Update task details, status, or assignment.
1276
+ - **ruflo__task_cancel** — Cancel a task.
1277
+
1278
+ ### Workflow Orchestration:
1279
+ - **ruflo__workflow_create** — Define a multi-step workflow with dependencies.
1280
+ - **ruflo__workflow_execute** — Run a workflow. \`{"workflowId": "wf-xxx"}\`
1281
+ - **ruflo__workflow_status** — Check workflow progress.
1282
+ - **ruflo__workflow_template** — Use a pre-built workflow template.
1283
+ - **ruflo__workflow_pause** / **ruflo__workflow_resume** — Control workflow execution.
1284
+
1285
+ ### Hive-Mind (Distributed Consensus):
1286
+ - **ruflo__hive-mind_init** — Start distributed consensus system.
1287
+ - **ruflo__hive-mind_spawn** — Add an agent to the hive.
1288
+ - **ruflo__hive-mind_consensus** — Run consensus vote across agents.
1289
+ - **ruflo__hive-mind_broadcast** — Send message to all hive agents.
1290
+ - **ruflo__hive-mind_memory** — Access shared hive memory.
1291
+
1292
+ ### Coordination:
1293
+ - **ruflo__coordination_topology** — View/change coordination topology.
1294
+ - **ruflo__coordination_load_balance** — Distribute work across agents.
1295
+ - **ruflo__coordination_orchestrate** — Orchestrate complex multi-agent tasks.
1296
+ - **ruflo__coordination_sync** — Synchronize state across agents.
1297
+
1298
+ ### Session Management:
1299
+ - **ruflo__session_save** — Save current session state.
1300
+ - **ruflo__session_restore** — Restore a previous session.
1301
+ - **ruflo__session_list** — List available sessions.
1302
+
1303
+ ## Group 4: Memory & Knowledge (ruflo)
1304
+ Persistent memory, vector search, embeddings, and pattern storage. ${TOOL_GROUPS.memory.enabled ? "ACTIVE" : "DISABLED"}
1305
+
1306
+ ### Memory Operations:
1307
+ - **ruflo__memory_store** — Store data in persistent memory.
1308
+ \`{"key": "my-key", "value": "data to store", "namespace": "default", "tags": ["tag1"]}\`
1309
+ - **ruflo__memory_retrieve** — Get stored data by key. \`{"key": "my-key"}\`
1310
+ - **ruflo__memory_search** — Semantic vector search across stored memories.
1311
+ \`{"query": "what to search for", "limit": 5, "namespace": "default"}\`
1312
+ - **ruflo__memory_list** — List all stored keys in a namespace.
1313
+ - **ruflo__memory_delete** — Remove a stored memory.
1314
+ - **ruflo__memory_stats** — View memory usage statistics.
1315
+
1316
+ ### Embeddings:
1317
+ - **ruflo__embeddings_generate** — Generate vector embeddings for text.
1318
+ - **ruflo__embeddings_compare** — Compare semantic similarity of two texts.
1319
+ - **ruflo__embeddings_search** — Search embeddings database by similarity.
1320
+ - **ruflo__embeddings_neural** — Generate neural embeddings.
1321
+ - **ruflo__embeddings_hyperbolic** — Generate hyperbolic embeddings for hierarchical data.
1322
+
1323
+ ### AgentDB (Advanced Pattern Storage):
1324
+ - **ruflo__agentdb_pattern-store** — Store a learned pattern with metadata.
1325
+ \`{"pattern": "description", "category": "code|debug|architecture", "confidence": 0.9}\`
1326
+ - **ruflo__agentdb_pattern-search** — Search patterns by similarity.
1327
+ - **ruflo__agentdb_route** — Route a query to the most relevant stored pattern.
1328
+ - **ruflo__agentdb_feedback** — Provide feedback on a pattern (reinforcement learning).
1329
+ - **ruflo__agentdb_context-synthesize** — Synthesize context from multiple sources.
1330
+ - **ruflo__agentdb_semantic-route** — Semantic routing based on stored knowledge.
1331
+ - **ruflo__agentdb_consolidate** — Consolidate and deduplicate stored patterns.
1332
+ - **ruflo__agentdb_batch** — Batch operations on patterns.
1333
+ - **ruflo__agentdb_session-start** / **ruflo__agentdb_session-end** — Session tracking.
1334
+ - **ruflo__agentdb_hierarchical-store** / **ruflo__agentdb_hierarchical-recall** — Hierarchical memory.
1335
+
1336
+ ## Group 5: Dev Tools & Analysis (ruflo)
1337
+ Performance, system health, GitHub integration, code analysis, terminal. ${TOOL_GROUPS.devtools.enabled ? "ACTIVE" : "DISABLED"}
1338
+
1339
+ ### System & Performance:
1340
+ - **ruflo__system_status** — System health overview.
1341
+ - **ruflo__system_metrics** — Detailed performance metrics.
1342
+ - **ruflo__system_health** — Health check across all subsystems.
1343
+ - **ruflo__performance_report** — Generate performance report.
1344
+ - **ruflo__performance_bottleneck** — Identify performance bottlenecks.
1345
+ - **ruflo__performance_benchmark** — Run benchmarks.
1346
+ - **ruflo__performance_optimize** — Get optimization recommendations.
1347
+ - **ruflo__performance_profile** — Profile specific operations.
1348
+
1349
+ ### Code Analysis:
1350
+ - **ruflo__analyze_diff** — Analyze a code diff.
1351
+ - **ruflo__analyze_diff-risk** — Assess risk level of changes.
1352
+ - **ruflo__analyze_diff-classify** — Classify type of changes (feature, bugfix, refactor).
1353
+ - **ruflo__analyze_diff-reviewers** — Suggest code reviewers.
1354
+ - **ruflo__analyze_file-risk** — Assess risk of a specific file.
1355
+
1356
+ ### GitHub Integration:
1357
+ - **ruflo__github_repo_analyze** — Analyze a GitHub repository.
1358
+ \`{"repo": "owner/repo", "analysis_type": "code_quality|performance|security"}\`
1359
+ - **ruflo__github_pr_manage** — Manage pull requests (create, review, merge).
1360
+ - **ruflo__github_issue_track** — Track and manage issues.
1361
+ - **ruflo__github_workflow** — Manage GitHub Actions workflows.
1362
+ - **ruflo__github_metrics** — Repository metrics and insights.
1363
+
1364
+ ### Terminal Access:
1365
+ - **ruflo__terminal_create** — Create a terminal session.
1366
+ - **ruflo__terminal_execute** — Execute a command. \`{"command": "ls -la"}\`
1367
+ - **ruflo__terminal_list** — List active terminals.
1368
+ - **ruflo__terminal_history** — View command history.
1369
+
1370
+ ### Development Hooks:
1371
+ - **ruflo__hooks_pre-task** / **ruflo__hooks_post-task** — Task lifecycle hooks for learning.
1372
+ - **ruflo__hooks_pre-edit** / **ruflo__hooks_post-edit** — File edit hooks.
1373
+ - **ruflo__hooks_session-start** / **ruflo__hooks_session-end** — Session lifecycle.
1374
+ - **ruflo__hooks_worker-dispatch** — Dispatch background workers.
1375
+ Workers: \`optimize\`, \`audit\`, \`testgaps\`, \`document\`, \`map\`, \`deepdive\`, \`benchmark\`
1376
+ - **ruflo__hooks_model-route** — Route to optimal AI model for a task.
1377
+ - **ruflo__hooks_explain** — Explain a routing or intelligence decision.
1378
+
1379
+ ### Configuration:
1380
+ - **ruflo__config_get** / **ruflo__config_set** / **ruflo__config_list** — Manage settings.
1381
+
1382
+ ### Progress Tracking:
1383
+ - **ruflo__progress_check** — Check implementation progress.
1384
+ - **ruflo__progress_summary** — Summarize overall progress.
1385
+
1386
+ # Decision Framework — FOLLOW THIS EXACTLY
1387
+
1388
+ 1. **ANY factual question** → \`web_research(action='search')\` IMMEDIATELY. Do NOT answer from memory.
1389
+ 2. **"What is X?"** → \`web_research(action='search', query='X')\`
1390
+ 3. **"Compare X vs Y"** → \`web_research(action='compare', query='X vs Y', items=['X', 'Y'])\`
1391
+ 4. **"Is it true that..."** → \`web_research(action='fact_check', query='...')\`
1392
+ 5. **Complex research** → \`web_research(action='goap', query='...')\` — auto multi-step pipeline
1393
+ 6. **Internal docs/procedures** → \`search(query='...')\` first, then web_research if not found
1394
+ 7. **Code task** → \`ruvector__hooks_route\` + \`ruflo__agent_spawn\`
1395
+ 8. **Remember something** → \`ruflo__memory_store\` / \`ruflo__memory_search\`
1396
+ 9. **"What can you do?"** → \`guidance(topic='overview')\`
1397
+ 10. **Unknown** → \`guidance(topic='overview')\`
1398
+
1399
+ IMPORTANT: For questions 1-5, ALWAYS call web_research. Never say "I don't have access to search" — you DO.
1400
+
1401
+ # Execution Patterns
1402
+
1403
+ ### Simple Question
1404
+ \`search\` or \`web_research\` → synthesize → respond
1405
+
1406
+ ### Complex Research
1407
+ \`web_research(action='goap')\` → analyze → respond with citations
1408
+
1409
+ ### Code Implementation
1410
+ \`ruvector__hooks_route\` → \`ruflo__agent_spawn(coder)\` → track with \`ruflo__task_create\` → report
1411
+
1412
+ ### Multi-Agent Analysis
1413
+ \`ruflo__swarm_init(hierarchical)\` → spawn agents → coordinate → synthesize results
1414
+
1415
+ ### Learning & Memory
1416
+ \`ruflo__memory_search\` (check existing) → do work → \`ruflo__memory_store\` (save results) → \`ruvector__hooks_learn\`
1417
+
1418
+ # Parallel Execution
1419
+
1420
+ When multiple independent tools can help, call them ALL in parallel:
1421
+ - Search + web_research simultaneously
1422
+ - Spawn multiple agents at once (coder + tester + reviewer)
1423
+ - Run analysis + performance + security tools in parallel
1424
+ NEVER call tools sequentially when they could run in parallel.
1425
+
1426
+ # Response Rules
1427
+
1428
+ 1. **Call tools FIRST**, then present results conversationally — NEVER show raw JSON to the user
1429
+ 2. Use markdown: **bold** headers, bullet points, numbered steps, tables for comparisons
1430
+ 3. Synthesize tool results naturally — be a helpful colleague, not a data pipe
1431
+ 4. Cite sources when available from web_research results
1432
+ 5. If a tool fails, say so honestly and try an alternative approach
1433
+ 6. For complex tasks, briefly outline your plan before executing
1434
+ 7. After completing work, suggest relevant follow-up actions
1435
+ 8. When spawning agents, explain what each agent will do
1436
+
1437
+ # Never Expose to User
1438
+
1439
+ - Raw JSON, similarity scores, chunk IDs, internal IDs, task IDs
1440
+ - Tool names, function names, API endpoints, backend names
1441
+ - References to "MCP", "tool calls", "vectors", "embeddings", infrastructure
1442
+ - The prefixes "ruflo__" or "ruvector__" — just describe what you're doing naturally
1443
+ - Error stack traces — summarize errors in plain language`;
1444
+ }
1445
+
1446
+ // =============================================================================
1447
+ // AUTOPILOT MODE — Server-side auto-continue loop (ADR-037)
1448
+ // =============================================================================
1449
+
1450
+ const detailStore = new Map(); // detailToken → full tool result (TTL: 5min)
1451
+
1452
+ const AUTOPILOT_SYSTEM_PROMPT = `
1453
+ You are in AUTOPILOT MODE. You should:
1454
+ 1. Break complex tasks into steps and execute them using available tools
1455
+ 2. Call MULTIPLE tools in parallel when they are independent
1456
+ 3. After each tool result, analyze it and decide the next action
1457
+ 4. Continue until the task is complete — do NOT ask the user for confirmation
1458
+ 5. Use memory_search to find relevant patterns before starting
1459
+ 6. Summarize your progress at each step
1460
+ 7. When done, provide a final summary of everything accomplished
1461
+
1462
+ Parallel execution patterns:
1463
+ - Research: memory_search + hooks_route + agent_spawn(researcher) — all in parallel
1464
+ - Code: agent_spawn(coder) + agent_spawn(tester) — parallel, then review
1465
+ - Analysis: search multiple sources in parallel → synthesize → report
1466
+ - Security: security_scan + hooks_route(audit) + memory_search(CVEs) — parallel
1467
+ `;
1468
+
1469
+ const AUTOPILOT_BLOCKED_PATTERNS = [
1470
+ /^deploy_/,
1471
+ /^security_delete/,
1472
+ /^browser_fill$/,
1473
+ /^browser_click$/,
1474
+ /terminal_execute/,
1475
+ ];
1476
+
1477
+ function isBlockedTool(name) {
1478
+ return AUTOPILOT_BLOCKED_PATTERNS.some(p => p.test(name));
1479
+ }
1480
+
1481
+ function sendAutopilotEvent(res, data) {
1482
+ res.write(`data: ${JSON.stringify(data)}\n\n`);
1483
+ }
1484
+
1485
+ function safeParseArgs(args) {
1486
+ if (typeof args === 'object' && args !== null) return args;
1487
+ try { return JSON.parse(args || '{}'); } catch { return {}; }
1488
+ }
1489
+
1490
+ function autopilotSleep(ms) {
1491
+ return new Promise(resolve => setTimeout(resolve, ms));
1492
+ }
1493
+
1494
+ async function handleAutopilot(req, res, provider, body) {
1495
+ const maxSteps = Math.min(parseInt(req.headers['x-autopilot-max-steps'] || '20', 10), 50);
1496
+ const cooldownMs = parseInt(process.env.AUTOPILOT_COOLDOWN || '500', 10);
1497
+ const stepTimeoutMs = parseInt(process.env.AUTOPILOT_STEP_TIMEOUT || '30000', 10);
1498
+
1499
+ // SSE setup
1500
+ res.setHeader('Content-Type', 'text/event-stream');
1501
+ res.setHeader('Cache-Control', 'no-cache');
1502
+ res.setHeader('Connection', 'keep-alive');
1503
+ res.setHeader('X-Accel-Buffering', 'no');
1504
+
1505
+ let messages = [...body.messages];
1506
+ let step = 0;
1507
+ let aborted = false;
1508
+ let totalTasks = 0;
1509
+ const startTime = Date.now();
1510
+
1511
+ req.on('close', () => { aborted = true; });
1512
+
1513
+ sendAutopilotEvent(res, { type: 'autopilot_start', maxSteps });
1514
+
1515
+ // Get the tools list for the AI provider (OpenAI function calling format)
1516
+ // Limit to BUILTIN_TOOLS + top external tools to avoid overwhelming the model
1517
+ // (Gemini struggles with 100+ tool definitions)
1518
+ const MAX_AUTOPILOT_TOOLS = 30;
1519
+ const externalTools = getActiveTools();
1520
+ // Prioritize tools from enabled high-value groups
1521
+ const priorityPrefixes = ['ruvector__hooks', 'ruvector__memory', 'ruflo__memory', 'ruflo__agent'];
1522
+ const prioritized = externalTools
1523
+ .sort((a, b) => {
1524
+ const aP = priorityPrefixes.some(p => a.name.startsWith(p)) ? 0 : 1;
1525
+ const bP = priorityPrefixes.some(p => b.name.startsWith(p)) ? 0 : 1;
1526
+ return aP - bP;
1527
+ })
1528
+ .slice(0, MAX_AUTOPILOT_TOOLS - BUILTIN_TOOLS.length);
1529
+ const allTools = [...BUILTIN_TOOLS, ...prioritized];
1530
+ const toolDefs = allTools.map(t => ({
1531
+ type: 'function',
1532
+ function: {
1533
+ name: t.name,
1534
+ description: t.description || '',
1535
+ parameters: t.inputSchema || { type: 'object', properties: {} },
1536
+ },
1537
+ }));
1538
+ console.log(`[autopilot] ${allTools.length} tools (${BUILTIN_TOOLS.length} builtin + ${prioritized.length} external)`);
1539
+
1540
+ while (step < maxSteps && !aborted) {
1541
+ // 1. Call upstream AI provider (non-streaming for tool call parsing)
1542
+ const apiKey = provider.getKey();
1543
+ let aiResult;
1544
+ try {
1545
+ const aiResponse = await fetch(provider.baseURL, {
1546
+ method: 'POST',
1547
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
1548
+ body: JSON.stringify({
1549
+ ...body,
1550
+ messages,
1551
+ stream: false,
1552
+ tools: toolDefs.length > 0 ? toolDefs : undefined,
1553
+ }),
1554
+ signal: AbortSignal.timeout(stepTimeoutMs),
1555
+ });
1556
+ aiResult = await aiResponse.json();
1557
+ } catch (err) {
1558
+ sendAutopilotEvent(res, { type: 'autopilot_error', error: `AI call failed: ${err.message}` });
1559
+ break;
1560
+ }
1561
+
1562
+ const choice = aiResult.choices?.[0];
1563
+ if (!choice) {
1564
+ sendAutopilotEvent(res, { type: 'autopilot_error', error: 'No response from AI' });
1565
+ break;
1566
+ }
1567
+
1568
+ // 2. Check for tool calls
1569
+ const toolCalls = choice.message?.tool_calls;
1570
+
1571
+ if (!toolCalls || toolCalls.length === 0) {
1572
+ // Final text response — send it
1573
+ sendAutopilotEvent(res, { type: 'autopilot_text', content: choice.message?.content || '' });
1574
+ break;
1575
+ }
1576
+
1577
+ // 3. Execute ALL tool calls in parallel
1578
+ step++;
1579
+ const groupId = `g${step}`;
1580
+ const taskEvents = toolCalls.map((tc, i) => ({
1581
+ taskId: `t${totalTasks + i + 1}`,
1582
+ tool: tc.function.name,
1583
+ args: safeParseArgs(tc.function.arguments),
1584
+ status: 'running',
1585
+ }));
1586
+ totalTasks += taskEvents.length;
1587
+
1588
+ // If the AI also included text content, stream it before tools
1589
+ if (choice.message?.content) {
1590
+ sendAutopilotEvent(res, { type: 'autopilot_text', content: choice.message.content });
1591
+ }
1592
+
1593
+ // Stream group start
1594
+ sendAutopilotEvent(res, { type: 'task_group_start', groupId, step, tasks: taskEvents });
1595
+
1596
+ // Append assistant message to conversation
1597
+ messages.push(choice.message);
1598
+
1599
+ // Execute tools in parallel
1600
+ const groupStart = Date.now();
1601
+ const results = await Promise.allSettled(
1602
+ toolCalls.map(async (tc, i) => {
1603
+ const taskId = taskEvents[i].taskId;
1604
+ const toolName = tc.function.name;
1605
+ const toolArgs = safeParseArgs(tc.function.arguments);
1606
+ const taskStart = Date.now();
1607
+
1608
+ // Check blocklist
1609
+ if (isBlockedTool(toolName)) {
1610
+ sendAutopilotEvent(res, {
1611
+ type: 'task_update', taskId, status: 'blocked',
1612
+ summary: `${toolName} requires confirmation`,
1613
+ duration: Date.now() - taskStart,
1614
+ });
1615
+ return { toolCallId: tc.id, blocked: true, toolName };
1616
+ }
1617
+
1618
+ try {
1619
+ const result = await executeTool(toolName, toolArgs);
1620
+ const resultStr = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
1621
+
1622
+ // Store full detail, generate token for lazy loading
1623
+ const detailToken = `dt_${taskId}_${Date.now()}`;
1624
+ detailStore.set(detailToken, resultStr);
1625
+
1626
+ // Stream task completion with summary only
1627
+ const summary = resultStr.length > 120
1628
+ ? resultStr.substring(0, 120).replace(/\n/g, ' ') + '...'
1629
+ : resultStr.replace(/\n/g, ' ');
1630
+
1631
+ sendAutopilotEvent(res, {
1632
+ type: 'task_update', taskId, status: 'completed',
1633
+ summary, duration: Date.now() - taskStart, detailToken,
1634
+ });
1635
+
1636
+ return { toolCallId: tc.id, content: resultStr };
1637
+ } catch (err) {
1638
+ sendAutopilotEvent(res, {
1639
+ type: 'task_update', taskId, status: 'failed',
1640
+ summary: err.message, duration: Date.now() - taskStart,
1641
+ });
1642
+ return { toolCallId: tc.id, content: `Error: ${err.message}` };
1643
+ }
1644
+ })
1645
+ );
1646
+
1647
+ // Stream group end
1648
+ sendAutopilotEvent(res, { type: 'task_group_end', groupId, step, duration: Date.now() - groupStart });
1649
+
1650
+ // Check if any tools were blocked — pause autopilot
1651
+ const blockedResults = results
1652
+ .filter(r => r.status === 'fulfilled' && r.value.blocked)
1653
+ .map(r => r.value);
1654
+ if (blockedResults.length > 0) {
1655
+ sendAutopilotEvent(res, {
1656
+ type: 'autopilot_paused',
1657
+ reason: 'blocked_tools',
1658
+ tools: blockedResults.map(b => b.toolName),
1659
+ });
1660
+ break;
1661
+ }
1662
+
1663
+ // Append tool results to messages
1664
+ for (const r of results) {
1665
+ if (r.status === 'fulfilled' && !r.value.blocked) {
1666
+ messages.push({
1667
+ role: 'tool',
1668
+ tool_call_id: r.value.toolCallId,
1669
+ content: r.value.content,
1670
+ });
1671
+ }
1672
+ }
1673
+
1674
+ // Cooldown to prevent runaway
1675
+ await autopilotSleep(cooldownMs);
1676
+ }
1677
+
1678
+ if (step >= maxSteps && !aborted) {
1679
+ sendAutopilotEvent(res, {
1680
+ type: 'autopilot_text',
1681
+ content: `\n⚠️ Autopilot reached max steps (${maxSteps}). Stopping.\n`,
1682
+ });
1683
+ }
1684
+
1685
+ sendAutopilotEvent(res, {
1686
+ type: 'autopilot_end',
1687
+ totalSteps: step,
1688
+ totalTasks,
1689
+ duration: Date.now() - startTime,
1690
+ });
1691
+
1692
+ res.write('data: [DONE]\n\n');
1693
+ res.end();
1694
+
1695
+ // Clean up detail store after 5 minutes
1696
+ const detailTTL = parseInt(process.env.AUTOPILOT_DETAIL_TTL || '300000', 10);
1697
+ setTimeout(() => {
1698
+ for (const [key] of detailStore) {
1699
+ if (key.startsWith('dt_')) detailStore.delete(key);
1700
+ }
1701
+ }, detailTTL);
1702
+ }
1703
+
1704
+ // Lazy detail loading endpoint
1705
+ app.get('/autopilot/detail/:token', (req, res) => {
1706
+ const content = detailStore.get(req.params.token);
1707
+ if (content) {
1708
+ res.json({ content });
1709
+ } else {
1710
+ res.status(404).json({ error: 'Detail expired or not found' });
1711
+ }
1712
+ });
1713
+
1714
+ // =============================================================================
1715
+ // CHAT COMPLETIONS PROXY
1716
+ // =============================================================================
1717
+
1718
+ app.post("/chat/completions", async (req, res) => {
1719
+ const model = req.body?.model;
1720
+ const providerName = resolveProvider(model);
1721
+ const provider = PROVIDER_ROUTES[providerName];
1722
+ const apiKey = provider.getKey();
1723
+
1724
+ if (!apiKey) return res.status(401).json({ error: { message: `No API key for provider: ${providerName}` } });
1725
+
1726
+ // Inject comprehensive system prompt as the first message
1727
+ const body = { ...req.body };
1728
+ if (body.messages && Array.isArray(body.messages)) {
1729
+ let systemPrompt = buildSystemPrompt();
1730
+ // Add autopilot instructions if autopilot mode is active
1731
+ const isAutopilot = req.headers['x-autopilot'] === 'true';
1732
+ if (isAutopilot) {
1733
+ systemPrompt = AUTOPILOT_SYSTEM_PROMPT + '\n\n' + systemPrompt;
1734
+ }
1735
+ // Prepend our system prompt before any existing messages
1736
+ const hasSystemMsg = body.messages[0]?.role === "system";
1737
+ if (hasSystemMsg) {
1738
+ // Merge with existing system message
1739
+ body.messages = [
1740
+ { role: "system", content: systemPrompt + "\n\n" + body.messages[0].content },
1741
+ ...body.messages.slice(1),
1742
+ ];
1743
+ } else {
1744
+ body.messages = [{ role: "system", content: systemPrompt }, ...body.messages];
1745
+ }
1746
+ }
1747
+
1748
+ // Route to autopilot handler if x-autopilot header is set
1749
+ if (req.headers['x-autopilot'] === 'true') {
1750
+ return handleAutopilot(req, res, provider, body);
1751
+ }
1752
+
1753
+ try {
1754
+ const upstream = await fetch(provider.baseURL, {
1755
+ method: "POST",
1756
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
1757
+ body: JSON.stringify(body),
1758
+ });
1759
+
1760
+ if (!upstream.ok) {
1761
+ const errBody = await upstream.text();
1762
+ console.error(`Proxy error [${providerName}/${model}]: ${upstream.status} ${errBody.substring(0, 200)}`);
1763
+ // Normalize all upstream errors into OpenAI-compatible format so the
1764
+ // Chat UI's OpenAI SDK can parse them instead of "400 (no body)".
1765
+ let errorMessage = `Upstream ${providerName} error (${upstream.status})`;
1766
+ try {
1767
+ const parsed = JSON.parse(errBody);
1768
+ // Gemini returns [{"error": {"message": "..."}}]
1769
+ if (Array.isArray(parsed) && parsed[0]?.error?.message) {
1770
+ errorMessage = parsed[0].error.message;
1771
+ // OpenAI/OpenRouter return {"error": {"message": "..."}}
1772
+ } else if (parsed.error?.message) {
1773
+ errorMessage = parsed.error.message;
1774
+ }
1775
+ } catch {}
1776
+ return res.status(upstream.status).json({
1777
+ error: { message: errorMessage, type: "upstream_error", code: upstream.status },
1778
+ });
1779
+ }
1780
+
1781
+ res.setHeader("Content-Type", upstream.headers.get("content-type") || "application/json");
1782
+
1783
+ if (req.body?.stream && upstream.body) {
1784
+ const reader = upstream.body.getReader();
1785
+ const decoder = new TextDecoder();
1786
+ try {
1787
+ while (true) {
1788
+ const { done, value } = await reader.read();
1789
+ if (done) break;
1790
+ res.write(decoder.decode(value, { stream: true }));
1791
+ }
1792
+ } catch (e) { /* stream closed */ }
1793
+ finally { res.end(); }
1794
+ } else {
1795
+ res.send(await upstream.text());
1796
+ }
1797
+ } catch (err) {
1798
+ console.error(`Proxy error [${providerName}/${model}]:`, err.message);
1799
+ res.status(502).json({ error: { message: `Upstream error: ${err.message}` } });
1800
+ }
1801
+ });
1802
+
1803
+ // =============================================================================
1804
+ // MODELS & HEALTH
1805
+ // =============================================================================
1806
+
1807
+ const KNOWN_MODELS = [
1808
+ "gemini-2.5-pro", "gemini-2.5-flash",
1809
+ "gpt-4.1", "gpt-4.1-mini", "gpt-4o", "gpt-4o-mini",
1810
+ "o3-mini", "o1-mini",
1811
+ ];
1812
+
1813
+ app.get("/models", (_, res) => {
1814
+ res.json({ object: "list", data: KNOWN_MODELS.map(id => ({
1815
+ id,
1816
+ object: "model",
1817
+ owned_by: "system",
1818
+ providers: [{ supports_tools: true }],
1819
+ })) });
1820
+ });
1821
+
1822
+ app.get("/health", (_, res) => {
1823
+ const backends = {};
1824
+ for (const [name, client] of mcpBackends) {
1825
+ backends[name] = { ready: client.ready, tools: client.tools.length };
1826
+ }
1827
+ const activeTools = getActiveTools();
1828
+ const groups = {};
1829
+ for (const [name, g] of Object.entries(TOOL_GROUPS)) {
1830
+ groups[name] = { enabled: g.enabled, source: g.source };
1831
+ }
1832
+ res.json({
1833
+ status: "ok", service: "mcp-bridge", version: "2.0.0",
1834
+ tools: { builtin: BUILTIN_TOOLS.length, external: activeTools.length, total: BUILTIN_TOOLS.length + activeTools.length },
1835
+ groups, backends,
1836
+ });
1837
+ });
1838
+
1839
+ // GET /groups — list tool groups and their status
1840
+ app.get("/groups", (_, res) => {
1841
+ const activeTools = getActiveTools();
1842
+ const result = {};
1843
+ for (const [name, g] of Object.entries(TOOL_GROUPS)) {
1844
+ const tools = name === "core" ? BUILTIN_TOOLS :
1845
+ activeTools.filter(t => {
1846
+ if (g.source !== t._backend) return false;
1847
+ if (!g.prefixes) return true;
1848
+ return g.prefixes.some(p => t._originalName.startsWith(p));
1849
+ });
1850
+ result[name] = {
1851
+ enabled: g.enabled,
1852
+ description: g.description,
1853
+ tools: tools.length,
1854
+ toolNames: tools.map(t => t.name).slice(0, 10),
1855
+ };
1856
+ }
1857
+ res.json(result);
1858
+ });
1859
+
1860
+ // =============================================================================
1861
+ // STARTUP
1862
+ // =============================================================================
1863
+
1864
+ async function main() {
1865
+ app.listen(PORT, () => {
1866
+ console.log(`MCP Bridge v2.0.0 on port ${PORT}`);
1867
+ const enabled = Object.entries(TOOL_GROUPS).filter(([, g]) => g.enabled).map(([n]) => n);
1868
+ console.log(`Active groups: ${enabled.join(", ")}`);
1869
+ });
1870
+
1871
+ const anyBackendNeeded = BACKEND_DEFS.some(isBackendNeeded);
1872
+ if (anyBackendNeeded) {
1873
+ console.log("Initializing MCP backends...");
1874
+ await initBackends();
1875
+ }
1876
+ }
1877
+
1878
+ main().catch(err => { console.error("Fatal:", err); process.exit(1); });