@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,1500 @@
1
+ # ADR-037: Autopilot Mode with Parallel Task UI, Web Workers & RuVector WASM
2
+
3
+ **Status:** Accepted
4
+ **Date:** 2026-03-05
5
+ **Related:** ADR-035 (MCP Tool Groups), ADR-029 (HF Chat UI), ADR-002 (WASM Core)
6
+
7
+ ## Context
8
+
9
+ HF Chat UI currently operates in a strict request-response cycle:
10
+
11
+ 1. User sends message
12
+ 2. AI responds (possibly calling MCP tools)
13
+ 3. Chat UI renders tool results inline as a flat list
14
+ 4. **AI stops and waits for the next user message**
15
+
16
+ This has two fundamental problems:
17
+
18
+ ### Problem 1: No Auto-Continue
19
+
20
+ Multi-step agentic workflows (research → plan → implement → test → report) require the user to manually prompt "continue" after every tool call. For complex tasks, this creates 5-15 unnecessary round-trips.
21
+
22
+ **Claude Code** solves this with a bypass permissions toggle that lets the agent run autonomously.
23
+
24
+ ### Problem 2: No Parallel Task Visibility
25
+
26
+ When the AI spawns multiple agents or runs concurrent tool calls, the UI shows them as a flat sequential list. There is no way to:
27
+
28
+ - See multiple tasks running in parallel with independent progress
29
+ - Collapse/expand individual task details to manage visual complexity
30
+ - Lazy-load task details only when the user expands them (memory efficiency)
31
+ - Manage agent swarms with browser-native performance
32
+
33
+ **Claude Code** shows parallel tool calls as collapsible cards — each with a header (tool name + status), expandable detail area, and real-time streaming. The collapsed state shows just the header; expanded shows full output. Multiple cards run simultaneously.
34
+
35
+ ### Problem 3: No In-Browser Agent Intelligence
36
+
37
+ All agent coordination runs server-side. The browser is a dumb terminal. With RuVector WASM compiled to WebAssembly, agent routing, memory search, pattern matching, and swarm topology can run directly in the browser — reducing latency, enabling offline capabilities, and offloading the server.
38
+
39
+ **agentic-flow@latest** provides the backend autopilot capability. **RuVector WASM** provides in-browser intelligence. **Web Workers** provide non-blocking parallel execution. This ADR combines all three.
40
+
41
+ ## Decision
42
+
43
+ Add three integrated capabilities to HF Chat UI:
44
+
45
+ 1. **Autopilot Mode** — auto-continue toggle (server-side loop in MCP bridge)
46
+ 2. **Parallel Task UI** — Claude Code-style collapsible task cards with lazy rendering
47
+ 3. **WASM Agent Runtime** — RuVector WASM + Web Workers for in-browser agent coordination
48
+
49
+ ---
50
+
51
+ ## Part 1: Autopilot Mode
52
+
53
+ ### UX Design
54
+
55
+ ```
56
+ ┌──────────────────────────────────────────────────────────────────┐
57
+ │ Chat messages... │
58
+ │ │
59
+ │ ┌─────────────────────────────────────────────────────────────┐ │
60
+ │ │ Type a message... [Send] │ │
61
+ │ └─────────────────────────────────────────────────────────────┘ │
62
+ │ [Stop] ⚡ Autopilot [ON] │
63
+ │ │
64
+ └──────────────────────────────────────────────────────────────────┘
65
+ ```
66
+
67
+ - **Toggle position**: Below the input box, right-aligned
68
+ - **Visual states**: OFF (muted/gray), ON (electric blue glow, `⚡` icon)
69
+ - **Stop button**: Appears during autopilot execution, cancels the loop
70
+ - **Step counter**: Shows `Step 3/20` during execution
71
+
72
+ ### How It Works
73
+
74
+ #### Standard Mode (Autopilot OFF)
75
+ ```
76
+ User → AI → [tool_call] → execute → show result → STOP (wait for user)
77
+ ```
78
+
79
+ #### Autopilot Mode (Autopilot ON)
80
+ ```
81
+ User → AI → [tool_calls] → execute all in parallel → feed results back to AI →
82
+ [more tool_calls] → execute → feed back → ... → text-only response → STOP
83
+ ```
84
+
85
+ ### Server-Side Autopilot Loop
86
+
87
+ The loop runs in the MCP bridge to avoid deep modifications to HF Chat UI's SvelteKit internals:
88
+
89
+ ```
90
+ ┌──────────────────────────────────────────────────────────────────────────┐
91
+ │ MCP Bridge v2.1 │
92
+ │ │
93
+ │ /chat/completions │
94
+ │ ┌────────────────────────────────────────────────────────────────────┐ │
95
+ │ │ │ │
96
+ │ │ 1. Receive request with x-autopilot: true │ │
97
+ │ │ │ │
98
+ │ │ 2. AUTOPILOT LOOP: │ │
99
+ │ │ a. Send messages to upstream AI (Gemini/OpenAI/OpenRouter) │ │
100
+ │ │ b. If response has tool_calls: │ │
101
+ │ │ - Execute ALL tool calls in parallel (Promise.allSettled) │ │
102
+ │ │ - Stream structured task events to client (SSE) │ │
103
+ │ │ - Append tool results to messages[] │ │
104
+ │ │ - Loop back to (a) │ │
105
+ │ │ c. If response is text-only: break, stream final response │ │
106
+ │ │ d. If max_steps reached: break with warning │ │
107
+ │ │ │ │
108
+ │ │ 3. Stream final response + done signal │ │
109
+ │ │ │ │
110
+ │ └────────────────────────────────────────────────────────────────────┘ │
111
+ └──────────────────────────────────────────────────────────────────────────┘
112
+ ```
113
+
114
+ ### Protocol: Structured SSE Events
115
+
116
+ Instead of flat text markers, the bridge streams **structured JSON events** that the Parallel Task UI can parse:
117
+
118
+ ```
119
+ // Stream opens
120
+ data: {"type":"autopilot_start","maxSteps":20}
121
+
122
+ // AI decides to call 3 tools in parallel
123
+ data: {"type":"task_group_start","groupId":"g1","step":1,"tasks":[
124
+ {"taskId":"t1","tool":"memory_search","args":{"query":"auth patterns"},"status":"running"},
125
+ {"taskId":"t2","tool":"agent_spawn","args":{"type":"researcher"},"status":"running"},
126
+ {"taskId":"t3","tool":"hooks_route","args":{"task":"security audit"},"status":"running"}
127
+ ]}
128
+
129
+ // Task t1 completes
130
+ data: {"type":"task_update","taskId":"t1","status":"completed","duration":230,
131
+ "summary":"3 patterns found","detail":"[full result hidden until expanded]",
132
+ "detailToken":"dt_a7f3"}
133
+
134
+ // Task t2 completes
135
+ data: {"type":"task_update","taskId":"t2","status":"completed","duration":1200,
136
+ "summary":"Agent researcher-8b2c spawned","detail":null,"detailToken":"dt_b8e2"}
137
+
138
+ // Task t3 completes
139
+ data: {"type":"task_update","taskId":"t3","status":"completed","duration":180,
140
+ "summary":"Routed to security-architect","detail":null,"detailToken":"dt_c9f1"}
141
+
142
+ // Group complete, AI continues
143
+ data: {"type":"task_group_end","groupId":"g1","step":1,"duration":1200}
144
+
145
+ // Next round — AI calls 2 more tools
146
+ data: {"type":"task_group_start","groupId":"g2","step":2,"tasks":[
147
+ {"taskId":"t4","tool":"security_scan","args":{"target":"./src"},"status":"running"},
148
+ {"taskId":"t5","tool":"agent_spawn","args":{"type":"coder"},"status":"running"}
149
+ ]}
150
+
151
+ // ... more updates ...
152
+
153
+ // AI produces final text
154
+ data: {"type":"autopilot_text","content":"Based on my analysis, here are the findings..."}
155
+
156
+ // Done
157
+ data: {"type":"autopilot_end","totalSteps":4,"totalTasks":9,"duration":12400}
158
+
159
+ data: [DONE]
160
+ ```
161
+
162
+ ### Detail Token Lazy Loading
163
+
164
+ Full tool results are NOT streamed inline — they are stored server-side and fetched on-demand when the user expands a task card:
165
+
166
+ ```
167
+ GET /autopilot/detail/dt_a7f3
168
+ → { "content": "[full 50KB memory search result]" }
169
+ ```
170
+
171
+ This keeps the SSE stream lightweight (summaries only) and avoids wasting browser memory on collapsed task details.
172
+
173
+ ---
174
+
175
+ ## Part 2: Parallel Task UI (Claude Code-Style)
176
+
177
+ ### Visual Design
178
+
179
+ When autopilot is running or the AI calls multiple tools, the chat renders **task cards** instead of flat text:
180
+
181
+ ```
182
+ ┌──────────────────────────────────────────────────────────────────────┐
183
+ │ 🤖 Assistant │
184
+ │ │
185
+ │ I'll analyze your codebase for security issues. Running 3 checks │
186
+ │ in parallel... │
187
+ │ │
188
+ │ ┌─ Step 1/4 ─────────────────────────────────────────────────────┐ │
189
+ │ │ │ │
190
+ │ │ ✅ memory_search 230ms [▼] │ │
191
+ │ │ ┌─────────────────────────────────────────────────────────┐ │ │
192
+ │ │ │ Found 3 patterns: │ │ │
193
+ │ │ │ 1. JWT validation (confidence: 0.94) │ │ │
194
+ │ │ │ 2. CORS configuration (confidence: 0.87) │ │ │
195
+ │ │ │ 3. Input sanitization (confidence: 0.82) │ │ │
196
+ │ │ └─────────────────────────────────────────────────────────┘ │ │
197
+ │ │ │ │
198
+ │ │ ✅ agent_spawn(researcher) 1.2s [▶] │ │
199
+ │ │ │ │
200
+ │ │ ⏳ hooks_route(security audit) ... [▶] │ │
201
+ │ │ │ │
202
+ │ └─────────────────────────────────────────────────────────────────┘ │
203
+ │ │
204
+ │ ┌─ Step 2/4 ─────────────────────────────────────────────────────┐ │
205
+ │ │ │ │
206
+ │ │ 🔄 security_scan(./src) ... [▶] │ │
207
+ │ │ 🔄 agent_spawn(coder) ... [▶] │ │
208
+ │ │ │ │
209
+ │ └─────────────────────────────────────────────────────────────────┘ │
210
+ │ │
211
+ │ ⚡ Autopilot running — Step 2/20 [Stop] │
212
+ │ │
213
+ └──────────────────────────────────────────────────────────────────────┘
214
+ ```
215
+
216
+ ### Task Card States
217
+
218
+ | State | Icon | Color | Description |
219
+ |-------|------|-------|-------------|
220
+ | `queued` | `○` | gray | Waiting to execute |
221
+ | `running` | `🔄` | blue pulse | Currently executing |
222
+ | `completed` | `✅` | green | Finished successfully |
223
+ | `failed` | `❌` | red | Error occurred |
224
+ | `blocked` | `⚠️` | amber | Requires user confirmation |
225
+ | `cancelled` | `⊘` | gray | Cancelled by user/timeout |
226
+
227
+ ### Task Card Component
228
+
229
+ ```svelte
230
+ <!-- src/lib/components/TaskCard.svelte -->
231
+ <script lang="ts">
232
+ import { onMount, onDestroy } from 'svelte';
233
+
234
+ export let taskId: string;
235
+ export let tool: string;
236
+ export let status: 'queued' | 'running' | 'completed' | 'failed' | 'blocked' | 'cancelled';
237
+ export let summary: string = '';
238
+ export let duration: number | null = null;
239
+ export let detailToken: string | null = null;
240
+ export let args: Record<string, any> = {};
241
+
242
+ let expanded = false;
243
+ let detail: string | null = null;
244
+ let loadingDetail = false;
245
+
246
+ // Status icons and colors
247
+ const STATUS_CONFIG = {
248
+ queued: { icon: '○', color: '#6b7280', pulse: false },
249
+ running: { icon: '🔄', color: '#3b82f6', pulse: true },
250
+ completed:{ icon: '✅', color: '#22c55e', pulse: false },
251
+ failed: { icon: '❌', color: '#ef4444', pulse: false },
252
+ blocked: { icon: '⚠️', color: '#f59e0b', pulse: true },
253
+ cancelled:{ icon: '⊘', color: '#6b7280', pulse: false },
254
+ };
255
+
256
+ $: config = STATUS_CONFIG[status];
257
+
258
+ // Lazy load detail only when expanded
259
+ async function toggleExpand() {
260
+ expanded = !expanded;
261
+ if (expanded && detail === null && detailToken) {
262
+ loadingDetail = true;
263
+ try {
264
+ const res = await fetch(`/autopilot/detail/${detailToken}`);
265
+ const data = await res.json();
266
+ detail = data.content;
267
+ } catch (e) {
268
+ detail = `Error loading detail: ${e.message}`;
269
+ }
270
+ loadingDetail = false;
271
+ }
272
+ }
273
+
274
+ // Free memory when collapsed
275
+ function collapse() {
276
+ expanded = false;
277
+ // Optionally release detail from memory after a delay
278
+ // detail = null; // uncomment for aggressive memory saving
279
+ }
280
+
281
+ // Format duration
282
+ $: durationStr = duration != null
283
+ ? duration < 1000 ? `${duration}ms` : `${(duration/1000).toFixed(1)}s`
284
+ : '...';
285
+
286
+ // Format tool name for display
287
+ $: displayName = tool.replace(/_/g, ' ').replace(/^\w/, c => c.toUpperCase());
288
+
289
+ // Compact args summary
290
+ $: argsStr = Object.entries(args)
291
+ .map(([k, v]) => typeof v === 'string' ? v : JSON.stringify(v))
292
+ .join(', ')
293
+ .substring(0, 60);
294
+ </script>
295
+
296
+ <div class="task-card" class:expanded class:pulse={config.pulse}>
297
+ <button class="task-header" on:click={toggleExpand}>
298
+ <span class="status-icon">{config.icon}</span>
299
+ <span class="tool-name" style="color: {config.color}">{tool}</span>
300
+ {#if argsStr}
301
+ <span class="tool-args">({argsStr})</span>
302
+ {/if}
303
+ <span class="spacer" />
304
+ {#if summary && !expanded}
305
+ <span class="summary">{summary}</span>
306
+ {/if}
307
+ <span class="duration">{durationStr}</span>
308
+ <span class="expand-icon">{expanded ? '▼' : '▶'}</span>
309
+ </button>
310
+
311
+ {#if expanded}
312
+ <div class="task-detail">
313
+ {#if loadingDetail}
314
+ <div class="loading">Loading...</div>
315
+ {:else if detail}
316
+ <pre class="detail-content">{detail}</pre>
317
+ {:else if summary}
318
+ <pre class="detail-content">{summary}</pre>
319
+ {:else}
320
+ <div class="empty">No detail available</div>
321
+ {/if}
322
+ </div>
323
+ {/if}
324
+ </div>
325
+
326
+ <style>
327
+ .task-card {
328
+ border: 1px solid #2a2a3e;
329
+ border-radius: 8px;
330
+ margin: 2px 0;
331
+ background: #12121f;
332
+ overflow: hidden;
333
+ transition: border-color 0.2s;
334
+ }
335
+ .task-card.expanded {
336
+ border-color: #3b82f6;
337
+ }
338
+ .task-card.pulse {
339
+ animation: pulse-border 2s infinite;
340
+ }
341
+ @keyframes pulse-border {
342
+ 0%, 100% { border-color: #2a2a3e; }
343
+ 50% { border-color: #3b82f6; }
344
+ }
345
+ .task-header {
346
+ display: flex;
347
+ align-items: center;
348
+ gap: 8px;
349
+ padding: 8px 12px;
350
+ width: 100%;
351
+ background: none;
352
+ border: none;
353
+ color: #e2e8f0;
354
+ cursor: pointer;
355
+ font-family: 'SF Mono', 'Fira Code', monospace;
356
+ font-size: 13px;
357
+ text-align: left;
358
+ }
359
+ .task-header:hover {
360
+ background: #1a1a2e;
361
+ }
362
+ .status-icon { flex-shrink: 0; }
363
+ .tool-name { font-weight: 600; flex-shrink: 0; }
364
+ .tool-args { color: #6b7280; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 200px; }
365
+ .spacer { flex: 1; }
366
+ .summary { color: #94a3b8; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 300px; }
367
+ .duration { color: #6b7280; font-size: 11px; flex-shrink: 0; min-width: 45px; text-align: right; }
368
+ .expand-icon { color: #6b7280; flex-shrink: 0; font-size: 10px; }
369
+ .task-detail {
370
+ border-top: 1px solid #2a2a3e;
371
+ padding: 12px;
372
+ max-height: 400px;
373
+ overflow-y: auto;
374
+ }
375
+ .detail-content {
376
+ margin: 0;
377
+ font-size: 12px;
378
+ line-height: 1.5;
379
+ color: #cbd5e1;
380
+ white-space: pre-wrap;
381
+ word-break: break-word;
382
+ }
383
+ .loading { color: #6b7280; font-size: 12px; }
384
+ .empty { color: #4b5563; font-size: 12px; font-style: italic; }
385
+ </style>
386
+ ```
387
+
388
+ ### Task Group Component (Step Container)
389
+
390
+ ```svelte
391
+ <!-- src/lib/components/TaskGroup.svelte -->
392
+ <script lang="ts">
393
+ import TaskCard from './TaskCard.svelte';
394
+
395
+ export let groupId: string;
396
+ export let step: number;
397
+ export let tasks: Array<{
398
+ taskId: string;
399
+ tool: string;
400
+ status: string;
401
+ summary?: string;
402
+ duration?: number;
403
+ detailToken?: string;
404
+ args?: Record<string, any>;
405
+ }>;
406
+ export let duration: number | null = null;
407
+ export let collapsed = false;
408
+
409
+ $: allDone = tasks.every(t => ['completed','failed','cancelled'].includes(t.status));
410
+ $: anyRunning = tasks.some(t => t.status === 'running');
411
+ $: failCount = tasks.filter(t => t.status === 'failed').length;
412
+ $: passCount = tasks.filter(t => t.status === 'completed').length;
413
+
414
+ // Auto-collapse completed groups after 2s to save screen space
415
+ $: if (allDone && !collapsed) {
416
+ setTimeout(() => { collapsed = true; }, 2000);
417
+ }
418
+ </script>
419
+
420
+ <div class="task-group" class:collapsed class:running={anyRunning}>
421
+ <button class="group-header" on:click={() => collapsed = !collapsed}>
422
+ <span class="step-badge">Step {step}</span>
423
+ <span class="task-count">
424
+ {passCount}/{tasks.length} tasks
425
+ {#if failCount > 0}
426
+ <span class="fail-count">({failCount} failed)</span>
427
+ {/if}
428
+ </span>
429
+ <span class="spacer" />
430
+ {#if duration}
431
+ <span class="group-duration">{(duration/1000).toFixed(1)}s</span>
432
+ {/if}
433
+ <span class="collapse-icon">{collapsed ? '▶' : '▼'}</span>
434
+ </button>
435
+
436
+ {#if !collapsed}
437
+ <div class="group-tasks">
438
+ {#each tasks as task (task.taskId)}
439
+ <TaskCard {...task} />
440
+ {/each}
441
+ </div>
442
+ {/if}
443
+ </div>
444
+
445
+ <style>
446
+ .task-group {
447
+ border: 1px solid #1e1e32;
448
+ border-radius: 10px;
449
+ margin: 8px 0;
450
+ background: #0d0d1a;
451
+ overflow: hidden;
452
+ }
453
+ .task-group.running {
454
+ border-color: #1e3a5f;
455
+ }
456
+ .group-header {
457
+ display: flex;
458
+ align-items: center;
459
+ gap: 10px;
460
+ padding: 8px 14px;
461
+ width: 100%;
462
+ background: #111128;
463
+ border: none;
464
+ color: #94a3b8;
465
+ cursor: pointer;
466
+ font-size: 12px;
467
+ }
468
+ .group-header:hover { background: #161633; }
469
+ .step-badge {
470
+ background: #1e293b;
471
+ color: #60a5fa;
472
+ padding: 2px 8px;
473
+ border-radius: 4px;
474
+ font-weight: 600;
475
+ font-size: 11px;
476
+ }
477
+ .task-count { color: #6b7280; }
478
+ .fail-count { color: #ef4444; }
479
+ .spacer { flex: 1; }
480
+ .group-duration { color: #6b7280; font-family: monospace; }
481
+ .collapse-icon { color: #6b7280; font-size: 10px; }
482
+ .group-tasks { padding: 4px 8px 8px; }
483
+ </style>
484
+ ```
485
+
486
+ ### Memory-Efficient Rendering Strategy
487
+
488
+ Task cards are designed to use **zero memory when collapsed**:
489
+
490
+ ```
491
+ ┌─────────────────────────────────────────────────────────────────┐
492
+ │ MEMORY MODEL │
493
+ │ │
494
+ │ COLLAPSED TASK CARD (~200 bytes): │
495
+ │ ┌─────────────────────────────────────────────┐ │
496
+ │ │ taskId: "t1" │ │
497
+ │ │ tool: "memory_search" │ │
498
+ │ │ status: "completed" │ │
499
+ │ │ summary: "3 patterns found" ← 1 line │ │
500
+ │ │ duration: 230 │ │
501
+ │ │ detailToken: "dt_a7f3" ← lazy ref │ │
502
+ │ │ detail: null ← NOT LOADED │ │
503
+ │ └─────────────────────────────────────────────┘ │
504
+ │ │
505
+ │ EXPANDED TASK CARD (~200 bytes + detail size): │
506
+ │ ┌─────────────────────────────────────────────┐ │
507
+ │ │ ... same fields ... │ │
508
+ │ │ detail: "[50KB full result]" ← LOADED │ │
509
+ │ └─────────────────────────────────────────────┘ │
510
+ │ │
511
+ │ COLLAPSED AGAIN (aggressive mode): │
512
+ │ ┌─────────────────────────────────────────────┐ │
513
+ │ │ ... same fields ... │ │
514
+ │ │ detail: null ← FREED │ │
515
+ │ └─────────────────────────────────────────────┘ │
516
+ │ │
517
+ │ With 100 tasks × 50KB details: │
518
+ │ All collapsed: 100 × 200B = 20KB │
519
+ │ All expanded: 100 × 50KB = 5MB │
520
+ │ Only 3 visible: 3 × 50KB + 97 × 200B = 170KB │
521
+ │ │
522
+ └─────────────────────────────────────────────────────────────────┘
523
+ ```
524
+
525
+ Key techniques:
526
+ 1. **Detail tokens** — full results stored server-side, fetched on expand
527
+ 2. **Null-on-collapse** — detail freed from memory when card collapses (optional aggressive mode)
528
+ 3. **Virtual scrolling** — only DOM-render task cards in viewport (for 100+ tasks)
529
+ 4. **Auto-collapse** — completed step groups auto-collapse after 2 seconds
530
+ 5. **Summary truncation** — collapsed cards show max 100 chars
531
+
532
+ ### Virtual Scrolling for Large Task Lists
533
+
534
+ When autopilot generates 50+ tasks, virtual scrolling prevents DOM bloat:
535
+
536
+ ```svelte
537
+ <!-- src/lib/components/VirtualTaskList.svelte -->
538
+ <script lang="ts">
539
+ import { onMount } from 'svelte';
540
+ import TaskGroup from './TaskGroup.svelte';
541
+
542
+ export let groups: Array<any> = [];
543
+
544
+ let containerEl: HTMLElement;
545
+ let visibleRange = { start: 0, end: 10 };
546
+ const ITEM_HEIGHT = 48; // approx height of collapsed group
547
+
548
+ function updateVisibleRange() {
549
+ if (!containerEl) return;
550
+ const scrollTop = containerEl.scrollTop;
551
+ const clientHeight = containerEl.clientHeight;
552
+ visibleRange = {
553
+ start: Math.max(0, Math.floor(scrollTop / ITEM_HEIGHT) - 2),
554
+ end: Math.min(groups.length, Math.ceil((scrollTop + clientHeight) / ITEM_HEIGHT) + 2),
555
+ };
556
+ }
557
+
558
+ onMount(() => {
559
+ containerEl?.addEventListener('scroll', updateVisibleRange, { passive: true });
560
+ return () => containerEl?.removeEventListener('scroll', updateVisibleRange);
561
+ });
562
+
563
+ $: visibleGroups = groups.slice(visibleRange.start, visibleRange.end);
564
+ $: topPadding = visibleRange.start * ITEM_HEIGHT;
565
+ $: bottomPadding = (groups.length - visibleRange.end) * ITEM_HEIGHT;
566
+ </script>
567
+
568
+ <div class="virtual-list" bind:this={containerEl}>
569
+ <div style="height: {topPadding}px" />
570
+ {#each visibleGroups as group (group.groupId)}
571
+ <TaskGroup {...group} />
572
+ {/each}
573
+ <div style="height: {bottomPadding}px" />
574
+ </div>
575
+
576
+ <style>
577
+ .virtual-list {
578
+ max-height: 600px;
579
+ overflow-y: auto;
580
+ scrollbar-width: thin;
581
+ }
582
+ </style>
583
+ ```
584
+
585
+ ---
586
+
587
+ ## Part 3: Web Workers for Non-Blocking Execution
588
+
589
+ All autopilot processing runs in Web Workers to keep the main thread responsive:
590
+
591
+ ```
592
+ ┌──────────────────────────────────────────────────────────────────────┐
593
+ │ BROWSER │
594
+ │ │
595
+ │ ┌────────────────────┐ ┌─────────────────────────────────────┐ │
596
+ │ │ MAIN THREAD │ │ WEB WORKERS │ │
597
+ │ │ │ │ │ │
598
+ │ │ • Svelte UI │ │ ┌─────────────────────────────┐ │ │
599
+ │ │ • User input │◄───▶│ │ AutopilotWorker │ │ │
600
+ │ │ • DOM rendering │ msg │ │ • SSE stream parsing │ │ │
601
+ │ │ • Task card state │ │ │ • Task state machine │ │ │
602
+ │ │ │ │ │ • Event batching (16ms) │ │ │
603
+ │ │ Only receives: │ │ │ • Abort controller │ │ │
604
+ │ │ - Batched UI │ │ └─────────────────────────────┘ │ │
605
+ │ │ updates │ │ │ │
606
+ │ │ - Final renders │ │ ┌─────────────────────────────┐ │ │
607
+ │ │ │ │ │ WasmAgentWorker │ │ │
608
+ │ │ Never blocks on: │ │ │ • RuVector WASM runtime │ │ │
609
+ │ │ - SSE parsing │ │ │ • Agent routing decisions │ │ │
610
+ │ │ - JSON processing │ │ │ • Memory/pattern search │ │ │
611
+ │ │ - WASM execution │ │ │ • Swarm topology mgmt │ │ │
612
+ │ │ │ │ └─────────────────────────────┘ │ │
613
+ │ │ │ │ │ │
614
+ │ │ │ │ ┌─────────────────────────────┐ │ │
615
+ │ │ │ │ │ DetailFetchWorker │ │ │
616
+ │ │ │ │ │ • Lazy detail loading │ │ │
617
+ │ │ │ │ │ • LRU cache (max 20 items) │ │ │
618
+ │ │ │ │ │ • Prefetch on hover │ │ │
619
+ │ │ │ │ └─────────────────────────────┘ │ │
620
+ │ │ │ │ │ │
621
+ │ └────────────────────┘ └─────────────────────────────────────┘ │
622
+ │ │
623
+ └──────────────────────────────────────────────────────────────────────┘
624
+ ```
625
+
626
+ ### AutopilotWorker
627
+
628
+ Handles the SSE stream from the MCP bridge, parses structured events, batches UI updates at 60fps:
629
+
630
+ ```typescript
631
+ // src/lib/workers/autopilot.worker.ts
632
+
633
+ interface TaskState {
634
+ taskId: string;
635
+ tool: string;
636
+ status: string;
637
+ summary?: string;
638
+ duration?: number;
639
+ detailToken?: string;
640
+ args?: Record<string, any>;
641
+ }
642
+
643
+ interface GroupState {
644
+ groupId: string;
645
+ step: number;
646
+ tasks: TaskState[];
647
+ duration?: number;
648
+ }
649
+
650
+ let groups: Map<string, GroupState> = new Map();
651
+ let abortController: AbortController | null = null;
652
+ let batchTimeout: number | null = null;
653
+ let pendingUpdates: any[] = [];
654
+
655
+ // Batch UI updates at 60fps to prevent main thread jank
656
+ function flushUpdates() {
657
+ if (pendingUpdates.length === 0) return;
658
+ self.postMessage({ type: 'batch_update', updates: pendingUpdates, groups: [...groups.values()] });
659
+ pendingUpdates = [];
660
+ batchTimeout = null;
661
+ }
662
+
663
+ function queueUpdate(update: any) {
664
+ pendingUpdates.push(update);
665
+ if (!batchTimeout) {
666
+ batchTimeout = setTimeout(flushUpdates, 16) as any; // ~60fps
667
+ }
668
+ }
669
+
670
+ self.onmessage = async (e: MessageEvent) => {
671
+ const { type, url, headers, body } = e.data;
672
+
673
+ if (type === 'start') {
674
+ abortController = new AbortController();
675
+ groups.clear();
676
+
677
+ try {
678
+ const response = await fetch(url, {
679
+ method: 'POST',
680
+ headers,
681
+ body: JSON.stringify(body),
682
+ signal: abortController.signal,
683
+ });
684
+
685
+ const reader = response.body!.getReader();
686
+ const decoder = new TextDecoder();
687
+ let buffer = '';
688
+
689
+ while (true) {
690
+ const { done, value } = await reader.read();
691
+ if (done) break;
692
+
693
+ buffer += decoder.decode(value, { stream: true });
694
+ const lines = buffer.split('\n');
695
+ buffer = lines.pop() || '';
696
+
697
+ for (const line of lines) {
698
+ if (!line.startsWith('data: ')) continue;
699
+ const data = line.slice(6).trim();
700
+ if (data === '[DONE]') {
701
+ flushUpdates();
702
+ self.postMessage({ type: 'done', groups: [...groups.values()] });
703
+ return;
704
+ }
705
+
706
+ try {
707
+ const event = JSON.parse(data);
708
+ handleEvent(event);
709
+ } catch {}
710
+ }
711
+ }
712
+ } catch (err: any) {
713
+ if (err.name !== 'AbortError') {
714
+ self.postMessage({ type: 'error', error: err.message });
715
+ }
716
+ }
717
+ }
718
+
719
+ if (type === 'stop') {
720
+ abortController?.abort();
721
+ flushUpdates();
722
+ self.postMessage({ type: 'stopped', groups: [...groups.values()] });
723
+ }
724
+ };
725
+
726
+ function handleEvent(event: any) {
727
+ switch (event.type) {
728
+ case 'autopilot_start':
729
+ queueUpdate({ type: 'start', maxSteps: event.maxSteps });
730
+ break;
731
+
732
+ case 'task_group_start':
733
+ groups.set(event.groupId, {
734
+ groupId: event.groupId,
735
+ step: event.step,
736
+ tasks: event.tasks,
737
+ });
738
+ queueUpdate({ type: 'group_start', group: groups.get(event.groupId) });
739
+ break;
740
+
741
+ case 'task_update':
742
+ for (const [, group] of groups) {
743
+ const task = group.tasks.find(t => t.taskId === event.taskId);
744
+ if (task) {
745
+ Object.assign(task, event);
746
+ queueUpdate({ type: 'task_update', taskId: event.taskId, ...event });
747
+ break;
748
+ }
749
+ }
750
+ break;
751
+
752
+ case 'task_group_end':
753
+ const group = groups.get(event.groupId);
754
+ if (group) group.duration = event.duration;
755
+ queueUpdate({ type: 'group_end', groupId: event.groupId, duration: event.duration });
756
+ break;
757
+
758
+ case 'autopilot_text':
759
+ queueUpdate({ type: 'text', content: event.content });
760
+ break;
761
+
762
+ case 'autopilot_end':
763
+ queueUpdate({ type: 'end', ...event });
764
+ break;
765
+ }
766
+ }
767
+ ```
768
+
769
+ ### DetailFetchWorker
770
+
771
+ Lazy-loads task details with LRU caching and hover-prefetch:
772
+
773
+ ```typescript
774
+ // src/lib/workers/detail-fetch.worker.ts
775
+
776
+ const cache = new Map<string, string>();
777
+ const MAX_CACHE = 20;
778
+ const accessOrder: string[] = [];
779
+
780
+ function evictLRU() {
781
+ while (cache.size > MAX_CACHE) {
782
+ const oldest = accessOrder.shift();
783
+ if (oldest) cache.delete(oldest);
784
+ }
785
+ }
786
+
787
+ self.onmessage = async (e: MessageEvent) => {
788
+ const { type, detailToken, bridgeUrl } = e.data;
789
+
790
+ if (type === 'fetch' || type === 'prefetch') {
791
+ // Check cache first
792
+ if (cache.has(detailToken)) {
793
+ const idx = accessOrder.indexOf(detailToken);
794
+ if (idx > -1) accessOrder.splice(idx, 1);
795
+ accessOrder.push(detailToken);
796
+ if (type === 'fetch') {
797
+ self.postMessage({ type: 'detail', detailToken, content: cache.get(detailToken) });
798
+ }
799
+ return;
800
+ }
801
+
802
+ try {
803
+ const res = await fetch(`${bridgeUrl}/autopilot/detail/${detailToken}`);
804
+ const data = await res.json();
805
+ cache.set(detailToken, data.content);
806
+ accessOrder.push(detailToken);
807
+ evictLRU();
808
+
809
+ if (type === 'fetch') {
810
+ self.postMessage({ type: 'detail', detailToken, content: data.content });
811
+ }
812
+ } catch (err: any) {
813
+ if (type === 'fetch') {
814
+ self.postMessage({ type: 'detail_error', detailToken, error: err.message });
815
+ }
816
+ }
817
+ }
818
+
819
+ if (type === 'evict') {
820
+ cache.delete(detailToken);
821
+ const idx = accessOrder.indexOf(detailToken);
822
+ if (idx > -1) accessOrder.splice(idx, 1);
823
+ }
824
+ };
825
+ ```
826
+
827
+ ---
828
+
829
+ ## Part 4: RuVector WASM In-Browser Agent Runtime
830
+
831
+ ### Why WASM in the Browser?
832
+
833
+ Currently, all intelligence runs server-side: the MCP bridge calls ruvector/ruflo via stdio, gets results, sends them back. This adds latency and server load for operations that could run client-side.
834
+
835
+ RuVector's core capabilities — vector search, pattern matching, agent routing, HNSW indexing — are written in Rust and compile to WASM. Running them in-browser enables:
836
+
837
+ | Capability | Server-Side | WASM In-Browser |
838
+ |------------|-------------|-----------------|
839
+ | Agent routing decision | ~200ms (network + compute) | ~2ms (local WASM) |
840
+ | Pattern search (HNSW) | ~50ms (network + compute) | ~0.5ms (local WASM) |
841
+ | Swarm topology visualization | N/A (text only) | Real-time canvas rendering |
842
+ | Offline agent management | Not possible | Full local capability |
843
+ | Memory search preview | Requires API call | Instant local search |
844
+ | Cost estimation | Server calculates | Instant local estimate |
845
+
846
+ ### Architecture
847
+
848
+ ```
849
+ ┌──────────────────────────────────────────────────────────────────────────┐
850
+ │ BROWSER — WASM AGENT RUNTIME │
851
+ │ │
852
+ │ ┌──────────────────────────────────────────────────────────────────┐ │
853
+ │ │ WasmAgentWorker │ │
854
+ │ │ │ │
855
+ │ │ ┌─────────────────────────────────────────────────────────┐ │ │
856
+ │ │ │ @ruvector/wasm (compiled from ruvector Rust crate) │ │ │
857
+ │ │ │ │ │ │
858
+ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │
859
+ │ │ │ │ HNSW Index │ │ Agent Router │ │ Pattern DB │ │ │ │
860
+ │ │ │ │ │ │ │ │ │ │ │ │
861
+ │ │ │ │ • add() │ │ • route() │ │ • store() │ │ │ │
862
+ │ │ │ │ • search() │ │ • score() │ │ • match() │ │ │ │
863
+ │ │ │ │ • delete() │ │ • rank() │ │ • learn() │ │ │ │
864
+ │ │ │ │ │ │ │ │ │ │ │ │
865
+ │ │ │ │ 150x faster │ │ 66+ agent │ │ EWC++ │ │ │ │
866
+ │ │ │ │ than JS │ │ types │ │ anti-forget │ │ │ │
867
+ │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │
868
+ │ │ │ │ │ │
869
+ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │
870
+ │ │ │ │ Swarm Mgr │ │ Cost Est. │ │ Tokenizer │ │ │ │
871
+ │ │ │ │ │ │ │ │ │ │ │ │
872
+ │ │ │ │ • topology │ │ • estimate()│ │ • count() │ │ │ │
873
+ │ │ │ │ • balance │ │ • budget() │ │ • truncate()│ │ │ │
874
+ │ │ │ │ • health │ │ • alert() │ │ • split() │ │ │ │
875
+ │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │
876
+ │ │ │ │ │ │
877
+ │ │ │ SharedArrayBuffer for zero-copy data between workers │ │ │
878
+ │ │ └─────────────────────────────────────────────────────────┘ │ │
879
+ │ │ │ │
880
+ │ └──────────────────────────────────────────────────────────────────┘ │
881
+ │ │
882
+ │ Communication: │
883
+ │ • Main thread ↔ Workers: postMessage (structured clone) │
884
+ │ • Worker ↔ Worker: SharedArrayBuffer + Atomics (zero-copy) │
885
+ │ • Worker ↔ WASM: direct memory access (linear memory) │
886
+ │ │
887
+ └──────────────────────────────────────────────────────────────────────────┘
888
+ ```
889
+
890
+ ### WASM Module Loading
891
+
892
+ ```typescript
893
+ // src/lib/wasm/ruvector-wasm.ts
894
+
895
+ let wasmInstance: any = null;
896
+ let wasmReady = false;
897
+
898
+ export async function initWasm(): Promise<void> {
899
+ if (wasmReady) return;
900
+
901
+ // Load WASM module (~800KB gzipped, cached by browser)
902
+ const module = await import('@ruvector/wasm');
903
+ await module.default(); // initialize WASM memory
904
+ wasmInstance = module;
905
+ wasmReady = true;
906
+ }
907
+
908
+ // Agent routing — runs in ~2ms vs ~200ms server-side
909
+ export function routeTask(taskDescription: string, context: string[]): AgentRecommendation[] {
910
+ if (!wasmReady) throw new Error('WASM not initialized');
911
+ return wasmInstance.route_task(taskDescription, context);
912
+ }
913
+
914
+ // HNSW pattern search — runs in ~0.5ms vs ~50ms server-side
915
+ export function searchPatterns(query: string, limit: number = 5): PatternMatch[] {
916
+ if (!wasmReady) throw new Error('WASM not initialized');
917
+ return wasmInstance.hnsw_search(query, limit);
918
+ }
919
+
920
+ // Swarm topology management
921
+ export function createSwarm(topology: string, maxAgents: number): SwarmState {
922
+ if (!wasmReady) throw new Error('WASM not initialized');
923
+ return wasmInstance.swarm_create(topology, maxAgents);
924
+ }
925
+
926
+ export function rebalanceSwarm(swarmId: string): SwarmState {
927
+ return wasmInstance.swarm_rebalance(swarmId);
928
+ }
929
+
930
+ // Cost estimation — instant, no API call needed
931
+ export function estimateCost(model: string, inputTokens: number, outputTokens: number): CostEstimate {
932
+ return wasmInstance.estimate_cost(model, inputTokens, outputTokens);
933
+ }
934
+
935
+ // Token counting — instant, for context window management
936
+ export function countTokens(text: string, model: string): number {
937
+ return wasmInstance.count_tokens(text, model);
938
+ }
939
+
940
+ interface AgentRecommendation {
941
+ agentType: string;
942
+ confidence: number;
943
+ reasoning: string;
944
+ }
945
+
946
+ interface PatternMatch {
947
+ key: string;
948
+ value: string;
949
+ similarity: number;
950
+ namespace: string;
951
+ }
952
+
953
+ interface SwarmState {
954
+ id: string;
955
+ topology: string;
956
+ agents: Array<{ id: string; type: string; status: string; load: number }>;
957
+ connections: Array<[string, string]>;
958
+ }
959
+
960
+ interface CostEstimate {
961
+ inputCost: number;
962
+ outputCost: number;
963
+ totalCost: number;
964
+ currency: string;
965
+ }
966
+ ```
967
+
968
+ ### WasmAgentWorker
969
+
970
+ Runs RuVector WASM in a dedicated Web Worker:
971
+
972
+ ```typescript
973
+ // src/lib/workers/wasm-agent.worker.ts
974
+
975
+ import { initWasm, routeTask, searchPatterns, createSwarm, rebalanceSwarm, estimateCost, countTokens } from '../wasm/ruvector-wasm';
976
+
977
+ let initialized = false;
978
+
979
+ self.onmessage = async (e: MessageEvent) => {
980
+ const { type, id, ...params } = e.data;
981
+
982
+ // Lazy init — only load WASM when first needed
983
+ if (!initialized) {
984
+ try {
985
+ await initWasm();
986
+ initialized = true;
987
+ } catch (err: any) {
988
+ self.postMessage({ id, type: 'error', error: `WASM init failed: ${err.message}` });
989
+ return;
990
+ }
991
+ }
992
+
993
+ try {
994
+ let result: any;
995
+
996
+ switch (type) {
997
+ case 'route_task':
998
+ result = routeTask(params.task, params.context || []);
999
+ break;
1000
+ case 'search_patterns':
1001
+ result = searchPatterns(params.query, params.limit);
1002
+ break;
1003
+ case 'create_swarm':
1004
+ result = createSwarm(params.topology, params.maxAgents);
1005
+ break;
1006
+ case 'rebalance_swarm':
1007
+ result = rebalanceSwarm(params.swarmId);
1008
+ break;
1009
+ case 'estimate_cost':
1010
+ result = estimateCost(params.model, params.inputTokens, params.outputTokens);
1011
+ break;
1012
+ case 'count_tokens':
1013
+ result = countTokens(params.text, params.model);
1014
+ break;
1015
+ default:
1016
+ result = { error: `Unknown type: ${type}` };
1017
+ }
1018
+
1019
+ self.postMessage({ id, type: 'result', result });
1020
+ } catch (err: any) {
1021
+ self.postMessage({ id, type: 'error', error: err.message });
1022
+ }
1023
+ };
1024
+ ```
1025
+
1026
+ ### WASM-Powered UI Features
1027
+
1028
+ The WASM runtime enables browser-native features impossible with server-only architecture:
1029
+
1030
+ #### 1. Instant Agent Routing Preview
1031
+
1032
+ Before autopilot starts, WASM previews which agents will be used:
1033
+
1034
+ ```
1035
+ ┌──────────────────────────────────────────────────────────────────┐
1036
+ │ You: "Audit security of the authentication module" │
1037
+ │ │
1038
+ │ ⚡ Autopilot will use: [Start] │
1039
+ │ ┌──────────────────────────────────────────────────────────┐ │
1040
+ │ │ 🛡️ security-architect (0.94) — Lead security analysis │ │
1041
+ │ │ 🔍 researcher (0.87) — Code pattern search │ │
1042
+ │ │ 🧪 tester (0.82) — Vulnerability testing │ │
1043
+ │ │ 📝 reviewer (0.76) — Finding documentation │ │
1044
+ │ │ │ │
1045
+ │ │ Est. 6-8 steps • ~45s • ~$0.03 (Gemini Flash) │ │
1046
+ │ └──────────────────────────────────────────────────────────┘ │
1047
+ │ │
1048
+ └──────────────────────────────────────────────────────────────────┘
1049
+ ```
1050
+
1051
+ All computed locally in WASM: agent routing (2ms), cost estimation (instant), step prediction (from pattern DB).
1052
+
1053
+ #### 2. Live Swarm Topology Visualization
1054
+
1055
+ During autopilot, render swarm topology as an interactive graph:
1056
+
1057
+ ```
1058
+ ┌──────────────────────────────────────────────────────────────────┐
1059
+ │ Swarm Topology (hierarchical, 5 agents) [Collapse ▼] │
1060
+ │ │
1061
+ │ ┌────────────┐ │
1062
+ │ │ coordinator│ │
1063
+ │ │ (idle) │ │
1064
+ │ └─────┬──────┘ │
1065
+ │ ┌───────────┼───────────┐ │
1066
+ │ ┌─────┴─────┐ ┌──┴───┐ ┌─────┴─────┐ │
1067
+ │ │ security- │ │coder │ │ researcher│ │
1068
+ │ │ architect │ │(busy)│ │ (busy) │ │
1069
+ │ │ (busy) │ └──────┘ └───────────┘ │
1070
+ │ └────────────┘ │
1071
+ │ ┌──────┐ │
1072
+ │ │tester│ │
1073
+ │ │(idle)│ │
1074
+ │ └──────┘ │
1075
+ │ │
1076
+ │ Agents: 5 • Active: 3 • Load: 60% • Topology: optimal │
1077
+ └──────────────────────────────────────────────────────────────────┘
1078
+ ```
1079
+
1080
+ Rendered with `<canvas>` in the WasmAgentWorker, transferred to main thread via `OffscreenCanvas.transferToImageBitmap()`.
1081
+
1082
+ #### 3. Real-Time Cost Tracker
1083
+
1084
+ WASM tokenizer counts tokens locally, shows running cost during autopilot:
1085
+
1086
+ ```
1087
+ ┌──────────────────────────────────────────────────────────────────┐
1088
+ │ ⚡ Autopilot — Step 4/20 [Stop] │
1089
+ │ Tokens: 12,340 in / 3,200 out • Cost: $0.018 • Budget: ∞ │
1090
+ └──────────────────────────────────────────────────────────────────┘
1091
+ ```
1092
+
1093
+ #### 4. Offline Pattern Cache
1094
+
1095
+ WASM HNSW index caches recent patterns in IndexedDB. When offline or slow network, pattern searches still work:
1096
+
1097
+ ```typescript
1098
+ // Fallback chain:
1099
+ // 1. WASM HNSW (local, ~0.5ms) → if hit, use it
1100
+ // 2. Server MCP (remote, ~50ms) → if online, use it
1101
+ // 3. IndexedDB cache (local, ~5ms) → stale but available
1102
+ ```
1103
+
1104
+ ### Package Structure
1105
+
1106
+ ```
1107
+ @ruvector/wasm (npm, prebuilt WASM)
1108
+ ├── pkg/
1109
+ │ ├── ruvector_wasm_bg.wasm (~800KB gzipped)
1110
+ │ ├── ruvector_wasm.js (JS bindings)
1111
+ │ └── ruvector_wasm.d.ts (TypeScript types)
1112
+ ├── src/
1113
+ │ ├── lib.rs (Rust source)
1114
+ │ ├── hnsw.rs (HNSW index)
1115
+ │ ├── router.rs (Agent routing)
1116
+ │ ├── swarm.rs (Swarm topology)
1117
+ │ ├── tokenizer.rs (Token counting)
1118
+ │ └── cost.rs (Cost estimation)
1119
+ └── package.json
1120
+
1121
+ chat-ui-mcp/chat-ui/
1122
+ ├── src/lib/
1123
+ │ ├── components/
1124
+ │ │ ├── AutopilotToggle.svelte (toggle button)
1125
+ │ │ ├── TaskCard.svelte (individual task card)
1126
+ │ │ ├── TaskGroup.svelte (step group container)
1127
+ │ │ ├── VirtualTaskList.svelte (virtual scrolling)
1128
+ │ │ ├── SwarmTopology.svelte (canvas topology graph)
1129
+ │ │ ├── CostTracker.svelte (token/cost display)
1130
+ │ │ └── AgentPreview.svelte (pre-execution routing preview)
1131
+ │ ├── workers/
1132
+ │ │ ├── autopilot.worker.ts (SSE stream processing)
1133
+ │ │ ├── wasm-agent.worker.ts (RuVector WASM runtime)
1134
+ │ │ └── detail-fetch.worker.ts (lazy detail loading + LRU cache)
1135
+ │ ├── wasm/
1136
+ │ │ └── ruvector-wasm.ts (WASM module loader + API)
1137
+ │ └── stores/
1138
+ │ ├── autopilot.ts (autopilot state store)
1139
+ │ ├── tasks.ts (task/group state store)
1140
+ │ └── wasm.ts (WASM readiness store)
1141
+ ```
1142
+
1143
+ ---
1144
+
1145
+ ## Part 5: MCP Bridge Autopilot Implementation
1146
+
1147
+ ### Structured Event Streaming
1148
+
1149
+ ```javascript
1150
+ // mcp-bridge/index.js — autopilot handler
1151
+
1152
+ async function handleAutopilot(req, res, upstreamUrl, headers, body) {
1153
+ const maxSteps = parseInt(req.headers['x-autopilot-max-steps'] || '20', 10);
1154
+ const streamSteps = req.headers['x-autopilot-stream-steps'] === 'true';
1155
+
1156
+ // SSE setup
1157
+ res.setHeader('Content-Type', 'text/event-stream');
1158
+ res.setHeader('Cache-Control', 'no-cache');
1159
+ res.setHeader('Connection', 'keep-alive');
1160
+ res.setHeader('X-Accel-Buffering', 'no'); // nginx compatibility
1161
+
1162
+ let messages = [...body.messages];
1163
+ let step = 0;
1164
+ let aborted = false;
1165
+ let totalTasks = 0;
1166
+ const detailStore = new Map(); // detailToken → full result
1167
+ const startTime = Date.now();
1168
+
1169
+ req.on('close', () => { aborted = true; });
1170
+
1171
+ sendEvent(res, { type: 'autopilot_start', maxSteps });
1172
+
1173
+ while (step < maxSteps && !aborted) {
1174
+ // 1. Call upstream AI provider (non-streaming for tool call parsing)
1175
+ const aiResponse = await fetch(upstreamUrl, {
1176
+ method: 'POST',
1177
+ headers,
1178
+ body: JSON.stringify({ ...body, messages, stream: false }),
1179
+ });
1180
+ const aiResult = await aiResponse.json();
1181
+ const choice = aiResult.choices?.[0];
1182
+ if (!choice) break;
1183
+
1184
+ // 2. Check for tool calls
1185
+ const toolCalls = choice.message?.tool_calls;
1186
+
1187
+ if (!toolCalls || toolCalls.length === 0) {
1188
+ // Final text response — stream it
1189
+ sendEvent(res, { type: 'autopilot_text', content: choice.message?.content || '' });
1190
+ break;
1191
+ }
1192
+
1193
+ // 3. Execute ALL tool calls in parallel
1194
+ step++;
1195
+ const groupId = `g${step}`;
1196
+ const taskEvents = toolCalls.map((tc, i) => ({
1197
+ taskId: `t${totalTasks + i + 1}`,
1198
+ tool: tc.function.name,
1199
+ args: safeParseArgs(tc.function.arguments),
1200
+ status: 'running',
1201
+ }));
1202
+ totalTasks += taskEvents.length;
1203
+
1204
+ // Stream group start
1205
+ sendEvent(res, { type: 'task_group_start', groupId, step, tasks: taskEvents });
1206
+
1207
+ // Append assistant message to conversation
1208
+ messages.push(choice.message);
1209
+
1210
+ // Execute tools in parallel
1211
+ const groupStart = Date.now();
1212
+ const results = await Promise.allSettled(
1213
+ toolCalls.map(async (tc, i) => {
1214
+ const taskId = taskEvents[i].taskId;
1215
+ const toolName = tc.function.name;
1216
+ const toolArgs = safeParseArgs(tc.function.arguments);
1217
+ const taskStart = Date.now();
1218
+
1219
+ // Check blocklist
1220
+ if (isBlockedTool(toolName)) {
1221
+ sendEvent(res, {
1222
+ type: 'task_update', taskId, status: 'blocked',
1223
+ summary: `${toolName} requires confirmation`,
1224
+ duration: Date.now() - taskStart,
1225
+ });
1226
+ return { toolCallId: tc.id, blocked: true, toolName };
1227
+ }
1228
+
1229
+ try {
1230
+ const result = await executeTool(toolName, toolArgs);
1231
+ const resultStr = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
1232
+
1233
+ // Store full detail, generate token for lazy loading
1234
+ const detailToken = `dt_${taskId}`;
1235
+ detailStore.set(detailToken, resultStr);
1236
+
1237
+ // Stream task completion with summary only
1238
+ const summary = resultStr.length > 120
1239
+ ? resultStr.substring(0, 120).replace(/\n/g, ' ') + '...'
1240
+ : resultStr.replace(/\n/g, ' ');
1241
+
1242
+ sendEvent(res, {
1243
+ type: 'task_update', taskId, status: 'completed',
1244
+ summary, duration: Date.now() - taskStart, detailToken,
1245
+ });
1246
+
1247
+ return { toolCallId: tc.id, content: resultStr };
1248
+ } catch (err) {
1249
+ sendEvent(res, {
1250
+ type: 'task_update', taskId, status: 'failed',
1251
+ summary: err.message, duration: Date.now() - taskStart,
1252
+ });
1253
+ return { toolCallId: tc.id, content: `Error: ${err.message}` };
1254
+ }
1255
+ })
1256
+ );
1257
+
1258
+ // Stream group end
1259
+ sendEvent(res, { type: 'task_group_end', groupId, step, duration: Date.now() - groupStart });
1260
+
1261
+ // Check if any tools were blocked — pause autopilot
1262
+ const blockedResults = results
1263
+ .filter(r => r.status === 'fulfilled' && r.value.blocked)
1264
+ .map(r => r.value);
1265
+ if (blockedResults.length > 0) {
1266
+ sendEvent(res, {
1267
+ type: 'autopilot_paused',
1268
+ reason: 'blocked_tools',
1269
+ tools: blockedResults.map(b => b.toolName),
1270
+ });
1271
+ break;
1272
+ }
1273
+
1274
+ // Append tool results to messages
1275
+ for (const r of results) {
1276
+ if (r.status === 'fulfilled' && !r.value.blocked) {
1277
+ messages.push({
1278
+ role: 'tool',
1279
+ tool_call_id: r.value.toolCallId,
1280
+ content: r.value.content,
1281
+ });
1282
+ }
1283
+ }
1284
+
1285
+ // Cooldown to prevent runaway
1286
+ await sleep(500);
1287
+ }
1288
+
1289
+ if (step >= maxSteps && !aborted) {
1290
+ sendEvent(res, {
1291
+ type: 'autopilot_text',
1292
+ content: `\n⚠️ Autopilot reached max steps (${maxSteps}). Stopping.\n`,
1293
+ });
1294
+ }
1295
+
1296
+ sendEvent(res, {
1297
+ type: 'autopilot_end',
1298
+ totalSteps: step,
1299
+ totalTasks,
1300
+ duration: Date.now() - startTime,
1301
+ });
1302
+
1303
+ res.write('data: [DONE]\n\n');
1304
+ res.end();
1305
+
1306
+ // Clean up detail store after 5 minutes
1307
+ setTimeout(() => detailStore.clear(), 5 * 60 * 1000);
1308
+ }
1309
+
1310
+ // Detail fetch endpoint
1311
+ app.get('/autopilot/detail/:token', (req, res) => {
1312
+ const content = detailStore.get(req.params.token);
1313
+ if (content) {
1314
+ res.json({ content });
1315
+ } else {
1316
+ res.status(404).json({ error: 'Detail expired or not found' });
1317
+ }
1318
+ });
1319
+
1320
+ function sendEvent(res, data) {
1321
+ res.write(`data: ${JSON.stringify(data)}\n\n`);
1322
+ }
1323
+
1324
+ function safeParseArgs(args) {
1325
+ try { return JSON.parse(args || '{}'); } catch { return {}; }
1326
+ }
1327
+
1328
+ function sleep(ms) {
1329
+ return new Promise(resolve => setTimeout(resolve, ms));
1330
+ }
1331
+
1332
+ const AUTOPILOT_BLOCKED_PATTERNS = [
1333
+ /^deploy_/,
1334
+ /^security_delete/,
1335
+ /^browser_fill$/,
1336
+ /^browser_click$/,
1337
+ ];
1338
+
1339
+ function isBlockedTool(name) {
1340
+ return AUTOPILOT_BLOCKED_PATTERNS.some(p => p.test(name));
1341
+ }
1342
+ ```
1343
+
1344
+ ---
1345
+
1346
+ ## Part 6: Integration with agentic-flow
1347
+
1348
+ When autopilot is ON and `MCP_GROUP_AGENTIC_FLOW=true`, the system prompt is augmented:
1349
+
1350
+ ```javascript
1351
+ const AUTOPILOT_SYSTEM_PROMPT = `
1352
+ You are in AUTOPILOT MODE. You should:
1353
+ 1. Break complex tasks into steps and execute them using available tools
1354
+ 2. Call MULTIPLE tools in parallel when they are independent
1355
+ 3. After each tool result, analyze it and decide the next action
1356
+ 4. Continue until the task is complete — do NOT ask the user for confirmation
1357
+ 5. Use agentic_flow_agent for complex multi-step operations when available
1358
+ 6. Use memory_search to find relevant patterns before starting
1359
+ 7. Summarize your progress at each step
1360
+ 8. When done, provide a final summary of everything accomplished
1361
+
1362
+ Parallel execution patterns:
1363
+ - Research: memory_search + hooks_route + agent_spawn(researcher) — all in parallel
1364
+ - Code: agent_spawn(coder) + agent_spawn(tester) — parallel, then review
1365
+ - Analysis: search multiple sources in parallel → synthesize → report
1366
+ - Security: security_scan + hooks_route(audit) + memory_search(CVEs) — parallel
1367
+ `;
1368
+ ```
1369
+
1370
+ ---
1371
+
1372
+ ## Part 7: Safety Controls
1373
+
1374
+ | Control | Default | Configurable | Description |
1375
+ |---------|---------|-------------|-------------|
1376
+ | **Max steps** | 20 | `x-autopilot-max-steps` header | Hard limit on tool call rounds |
1377
+ | **Step timeout** | 30s | `AUTOPILOT_STEP_TIMEOUT` env | Per-tool execution timeout |
1378
+ | **Cooldown** | 500ms | `AUTOPILOT_COOLDOWN` env | Delay between steps |
1379
+ | **Stop button** | Always visible | N/A | User can abort at any time |
1380
+ | **Blocked tools** | deploy, destructive ops | `AUTOPILOT_BLOCKED_TOOLS` env | Tools requiring confirmation |
1381
+ | **Cost guard** | Disabled | `AUTOPILOT_MAX_COST` env | Stop if cost exceeds threshold |
1382
+ | **Token limit** | None | `AUTOPILOT_MAX_TOKENS` env | Stop if total tokens exceed limit |
1383
+ | **Detail TTL** | 5 min | `AUTOPILOT_DETAIL_TTL` env | How long full results are kept |
1384
+ | **WASM memory** | 64MB | `RUVECTOR_WASM_MEMORY` | Max WASM heap size |
1385
+ | **Detail cache** | 20 items | Hardcoded | LRU cache size in DetailFetchWorker |
1386
+
1387
+ ---
1388
+
1389
+ ## Part 8: Use Cases
1390
+
1391
+ The parallel task UI + autopilot + WASM runtime enables Claude Code-style workflows in the browser:
1392
+
1393
+ ### 1. Codebase Analysis
1394
+ ```
1395
+ User: "Analyze security of the auth module"
1396
+ → Autopilot spawns: security-architect, researcher, tester (parallel)
1397
+ → Each reports findings in collapsible task cards
1398
+ → AI synthesizes into final report
1399
+ ```
1400
+
1401
+ ### 2. Multi-Agent Research
1402
+ ```
1403
+ User: "Compare React, Vue, and Svelte for our use case"
1404
+ → Spawns 3 researcher agents in parallel
1405
+ → Each researches one framework
1406
+ → AI produces comparison table
1407
+ ```
1408
+
1409
+ ### 3. Full Development Cycle
1410
+ ```
1411
+ User: "Add rate limiting to the API"
1412
+ → Step 1: memory_search (patterns) + hooks_route (optimal agents)
1413
+ → Step 2: agent_spawn(architect) → produces design
1414
+ → Step 3: agent_spawn(coder) + agent_spawn(tester) (parallel)
1415
+ → Step 4: agent_spawn(reviewer) → produces review
1416
+ → Step 5: Final summary with code links
1417
+ ```
1418
+
1419
+ ### 4. Swarm Orchestration
1420
+ ```
1421
+ User: "Scrape pricing from 50 competitor websites"
1422
+ → WASM creates swarm topology (hierarchical, 10 agents)
1423
+ → Autopilot spawns navigator + 5 scrapers + 3 validators + monitor
1424
+ → Live topology graph shows agent status
1425
+ → Collapsible cards show per-site results
1426
+ → Final summary with data table
1427
+ ```
1428
+
1429
+ ### 5. Monitoring Dashboard
1430
+ ```
1431
+ User: "Monitor all our Cloud Run services"
1432
+ → Autopilot runs health checks on each service (parallel)
1433
+ → Task cards show service status (green/red)
1434
+ → WASM cost tracker shows API usage
1435
+ → Auto-refreshes every 60s in autopilot mode
1436
+ ```
1437
+
1438
+ ---
1439
+
1440
+ ## What Changes
1441
+
1442
+ | Component | Change |
1443
+ |-----------|--------|
1444
+ | **MCP Bridge** | Autopilot loop, structured SSE events, detail store, `/autopilot/detail/:token` endpoint |
1445
+ | **Chat UI** | `AutopilotToggle`, `TaskCard`, `TaskGroup`, `VirtualTaskList`, `SwarmTopology`, `CostTracker`, `AgentPreview` components |
1446
+ | **Chat UI** | 3 Web Workers: `autopilot.worker.ts`, `wasm-agent.worker.ts`, `detail-fetch.worker.ts` |
1447
+ | **Chat UI** | WASM module loader + Svelte stores for state management |
1448
+ | **Docker** | `AUTOPILOT_*` env vars, `@ruvector/wasm` dependency |
1449
+ | **npm** | New `@ruvector/wasm` package (prebuilt WASM, ~800KB gzipped) |
1450
+
1451
+ ## What Stays the Same
1452
+
1453
+ - All MCP tools, per-group endpoints, security, memory — unchanged
1454
+ - Standard (non-autopilot) chat flow — unchanged
1455
+ - Authentication (OIDC) — unchanged
1456
+ - Docker Compose structure — unchanged
1457
+ - MCP bridge backwards compatibility — unchanged
1458
+
1459
+ ## Consequences
1460
+
1461
+ ### Positive
1462
+
1463
+ - **Claude Code UX in browser** — parallel tasks, collapsible details, real-time progress
1464
+ - **Zero memory waste** — collapsed cards use ~200 bytes; details load on demand
1465
+ - **Non-blocking UI** — all heavy processing in Web Workers, main thread stays responsive
1466
+ - **In-browser intelligence** — WASM agent routing/search in ~2ms vs ~200ms server-side
1467
+ - **Eliminates continue fatigue** — autopilot runs complex tasks to completion
1468
+ - **Offline capable** — WASM pattern search + IndexedDB cache work without network
1469
+ - **Backward compatible** — autopilot OFF by default, existing flow unchanged
1470
+ - **Versatile** — same UI for code analysis, research, scraping, monitoring, deployment
1471
+
1472
+ ### Negative
1473
+
1474
+ - **WASM module size** — ~800KB initial download (cached after first load)
1475
+ - **Web Worker complexity** — 3 workers with message passing adds architectural complexity
1476
+ - **Token cost** — autopilot uses more tokens (no human filtering between steps)
1477
+ - **Error cascade** — wrong tool call in step 2 may cascade through steps 3-20
1478
+ - **Browser compatibility** — Web Workers + WASM requires modern browser (Chrome 80+, Firefox 78+, Safari 14+)
1479
+
1480
+ ### Risks & Mitigations
1481
+
1482
+ | Risk | Mitigation |
1483
+ |------|------------|
1484
+ | Runaway loops | Hard max steps (20), per-step timeout (30s), cooldown (500ms) |
1485
+ | Destructive actions | Blocked tool list, confirmation modal for dangerous tools |
1486
+ | High token cost | WASM cost tracker, optional budget limit, step counter |
1487
+ | WASM init failure | Graceful fallback to server-only mode (no WASM features) |
1488
+ | Memory bloat | Virtual scrolling, LRU detail cache (20 items), null-on-collapse |
1489
+ | Worker crash | Error boundaries, auto-restart with exponential backoff |
1490
+ | Stale patterns | WASM HNSW syncs with server on reconnect |
1491
+
1492
+ ## Related
1493
+
1494
+ - [ADR-035: MCP Tool Groups](ADR-035-MCP-TOOL-GROUPS.md) — per-group tool organization
1495
+ - [ADR-029: HF Chat UI](ADR-029-HUGGINGFACE-CHAT-UI-CLOUD-RUN.md) — base deployment
1496
+ - [ADR-002: WASM Core Package](ADR-002-WASM-CORE-PACKAGE.md) — WASM architecture
1497
+ - [ADR-036: Servo Browser MCP](ADR-036-SERVO-RUST-BROWSER-MCP.md) — Rust/WASM browser engine
1498
+ - [agentic-flow](https://www.npmjs.com/package/agentic-flow) — autonomous agent backend
1499
+ - [ruvector](https://www.npmjs.com/package/ruvector) — WASM-compiled intelligence runtime
1500
+ - Claude Code — UX inspiration for parallel tool cards and bypass mode