@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.
- package/README.md +410 -0
- package/bin/ruflo.js +57 -0
- package/package.json +98 -0
- package/src/chat-ui/Dockerfile +25 -0
- package/src/chat-ui/patch-mcp-url-safety.sh +28 -0
- package/src/chat-ui/static/chatui/icon-144x144.png +0 -0
- package/src/chat-ui/static/chatui/omni-welcome.gif +0 -0
- package/src/config/config.example.json +76 -0
- package/src/mcp-bridge/Dockerfile +45 -0
- package/src/mcp-bridge/index.js +1668 -0
- package/src/mcp-bridge/mcp-stdio-kernel.js +159 -0
- package/src/mcp-bridge/package.json +17 -0
- package/src/mcp-bridge/test-harness.js +470 -0
- package/src/nginx/Dockerfile +10 -0
- package/src/nginx/nginx.conf +67 -0
- package/src/nginx/static/favicon-dark.svg +4 -0
- package/src/nginx/static/favicon.svg +4 -0
- package/src/nginx/static/icon.svg +5 -0
- package/src/nginx/static/logo.svg +9 -0
- package/src/nginx/static/manifest.json +22 -0
- package/src/nginx/static/welcome.js +184 -0
- package/src/ruvocal/.claude/skills/add-model-descriptions/SKILL.md +73 -0
- package/src/ruvocal/.devcontainer/Dockerfile +9 -0
- package/src/ruvocal/.devcontainer/devcontainer.json +36 -0
- package/src/ruvocal/.dockerignore +17 -0
- package/src/ruvocal/.eslintignore +13 -0
- package/src/ruvocal/.eslintrc.cjs +45 -0
- package/src/ruvocal/.gcloudignore +18 -0
- package/src/ruvocal/.github/ISSUE_TEMPLATE/bug-report--chat-ui-.md +43 -0
- package/src/ruvocal/.github/ISSUE_TEMPLATE/config-support.md +9 -0
- package/src/ruvocal/.github/ISSUE_TEMPLATE/feature-request--chat-ui-.md +17 -0
- package/src/ruvocal/.github/ISSUE_TEMPLATE/huggingchat.md +11 -0
- package/src/ruvocal/.github/release.yml +16 -0
- package/src/ruvocal/.github/workflows/build-docs.yml +18 -0
- package/src/ruvocal/.github/workflows/build-image.yml +142 -0
- package/src/ruvocal/.github/workflows/build-pr-docs.yml +20 -0
- package/src/ruvocal/.github/workflows/deploy-dev.yml +63 -0
- package/src/ruvocal/.github/workflows/deploy-prod.yml +78 -0
- package/src/ruvocal/.github/workflows/lint-and-test.yml +84 -0
- package/src/ruvocal/.github/workflows/slugify.yaml +72 -0
- package/src/ruvocal/.github/workflows/trufflehog.yml +17 -0
- package/src/ruvocal/.github/workflows/upload-pr-documentation.yml +16 -0
- package/src/ruvocal/.husky/lint-stage-config.js +4 -0
- package/src/ruvocal/.husky/pre-commit +2 -0
- package/src/ruvocal/.prettierignore +14 -0
- package/src/ruvocal/.prettierrc +7 -0
- package/src/ruvocal/CLAUDE.md +126 -0
- package/src/ruvocal/Dockerfile +96 -0
- package/src/ruvocal/LICENSE +203 -0
- package/src/ruvocal/PRIVACY.md +41 -0
- package/src/ruvocal/README.md +164 -0
- package/src/ruvocal/chart/Chart.yaml +5 -0
- package/src/ruvocal/chart/env/dev.yaml +260 -0
- package/src/ruvocal/chart/env/prod.yaml +273 -0
- package/src/ruvocal/chart/templates/_helpers.tpl +22 -0
- package/src/ruvocal/chart/templates/config.yaml +10 -0
- package/src/ruvocal/chart/templates/deployment.yaml +81 -0
- package/src/ruvocal/chart/templates/hpa.yaml +45 -0
- package/src/ruvocal/chart/templates/infisical.yaml +24 -0
- package/src/ruvocal/chart/templates/ingress-internal.yaml +32 -0
- package/src/ruvocal/chart/templates/ingress.yaml +32 -0
- package/src/ruvocal/chart/templates/network-policy.yaml +36 -0
- package/src/ruvocal/chart/templates/service-account.yaml +13 -0
- package/src/ruvocal/chart/templates/service-monitor.yaml +17 -0
- package/src/ruvocal/chart/templates/service.yaml +21 -0
- package/src/ruvocal/chart/values.yaml +73 -0
- package/src/ruvocal/cloudbuild.yaml +68 -0
- package/src/ruvocal/config/branding.env.example +19 -0
- package/src/ruvocal/docker-compose.yml +21 -0
- package/src/ruvocal/docs/adr/ADR-029-HUGGINGFACE-CHAT-UI-CLOUD-RUN.md +1236 -0
- package/src/ruvocal/docs/adr/ADR-033-RUVECTOR-RUFLO-MCP-INTEGRATION.md +111 -0
- package/src/ruvocal/docs/adr/ADR-034-OPTIONAL-MCP-BACKENDS.md +117 -0
- package/src/ruvocal/docs/adr/ADR-035-MCP-TOOL-GROUPS.md +186 -0
- package/src/ruvocal/docs/adr/ADR-037-AUTOPILOT-CHAT-MODE.md +1500 -0
- package/src/ruvocal/docs/adr/ADR-038-RUVOCAL-FORK.md +286 -0
- package/src/ruvocal/docs/source/_toctree.yml +30 -0
- package/src/ruvocal/docs/source/configuration/common-issues.md +38 -0
- package/src/ruvocal/docs/source/configuration/llm-router.md +105 -0
- package/src/ruvocal/docs/source/configuration/mcp-tools.md +84 -0
- package/src/ruvocal/docs/source/configuration/metrics.md +9 -0
- package/src/ruvocal/docs/source/configuration/open-id.md +57 -0
- package/src/ruvocal/docs/source/configuration/overview.md +89 -0
- package/src/ruvocal/docs/source/configuration/theming.md +20 -0
- package/src/ruvocal/docs/source/developing/architecture.md +48 -0
- package/src/ruvocal/docs/source/index.md +53 -0
- package/src/ruvocal/docs/source/installation/docker.md +43 -0
- package/src/ruvocal/docs/source/installation/helm.md +43 -0
- package/src/ruvocal/docs/source/installation/local.md +62 -0
- package/src/ruvocal/entrypoint.sh +19 -0
- package/src/ruvocal/mcp-bridge/Dockerfile +45 -0
- package/src/ruvocal/mcp-bridge/cloudbuild.yaml +49 -0
- package/src/ruvocal/mcp-bridge/index.js +1878 -0
- package/src/ruvocal/mcp-bridge/mcp-stdio-kernel.js +159 -0
- package/src/ruvocal/mcp-bridge/package-lock.json +762 -0
- package/src/ruvocal/mcp-bridge/package.json +17 -0
- package/src/ruvocal/mcp-bridge/test-harness.js +470 -0
- package/src/ruvocal/models/add-your-models-here.txt +1 -0
- package/src/ruvocal/package-lock.json +11741 -0
- package/src/ruvocal/package.json +121 -0
- package/src/ruvocal/postcss.config.js +6 -0
- package/src/ruvocal/rvf.manifest.json +204 -0
- package/src/ruvocal/scripts/config.ts +64 -0
- package/src/ruvocal/scripts/generate-welcome.mjs +181 -0
- package/src/ruvocal/scripts/populate.ts +288 -0
- package/src/ruvocal/scripts/samples.txt +194 -0
- package/src/ruvocal/scripts/setups/vitest-setup-client.ts +0 -0
- package/src/ruvocal/scripts/setups/vitest-setup-server.ts +44 -0
- package/src/ruvocal/scripts/updateLocalEnv.ts +48 -0
- package/src/ruvocal/src/ambient.d.ts +7 -0
- package/src/ruvocal/src/app.d.ts +29 -0
- package/src/ruvocal/src/app.html +53 -0
- package/src/ruvocal/src/hooks.server.ts +32 -0
- package/src/ruvocal/src/hooks.ts +6 -0
- package/src/ruvocal/src/lib/APIClient.ts +148 -0
- package/src/ruvocal/src/lib/actions/clickOutside.ts +18 -0
- package/src/ruvocal/src/lib/actions/snapScrollToBottom.ts +346 -0
- package/src/ruvocal/src/lib/buildPrompt.ts +33 -0
- package/src/ruvocal/src/lib/components/AnnouncementBanner.svelte +20 -0
- package/src/ruvocal/src/lib/components/BackgroundGenerationPoller.svelte +168 -0
- package/src/ruvocal/src/lib/components/CodeBlock.svelte +73 -0
- package/src/ruvocal/src/lib/components/CopyToClipBoardBtn.svelte +92 -0
- package/src/ruvocal/src/lib/components/DeleteConversationModal.svelte +75 -0
- package/src/ruvocal/src/lib/components/EditConversationModal.svelte +100 -0
- package/src/ruvocal/src/lib/components/ExpandNavigation.svelte +22 -0
- package/src/ruvocal/src/lib/components/FoundationBackground.svelte +242 -0
- package/src/ruvocal/src/lib/components/HoverTooltip.svelte +44 -0
- package/src/ruvocal/src/lib/components/HtmlPreviewModal.svelte +143 -0
- package/src/ruvocal/src/lib/components/InfiniteScroll.svelte +50 -0
- package/src/ruvocal/src/lib/components/MobileNav.svelte +300 -0
- package/src/ruvocal/src/lib/components/Modal.svelte +115 -0
- package/src/ruvocal/src/lib/components/ModelCardMetadata.svelte +71 -0
- package/src/ruvocal/src/lib/components/NavConversationItem.svelte +151 -0
- package/src/ruvocal/src/lib/components/NavMenu.svelte +313 -0
- package/src/ruvocal/src/lib/components/Pagination.svelte +97 -0
- package/src/ruvocal/src/lib/components/PaginationArrow.svelte +27 -0
- package/src/ruvocal/src/lib/components/Portal.svelte +24 -0
- package/src/ruvocal/src/lib/components/RetryBtn.svelte +18 -0
- package/src/ruvocal/src/lib/components/RuFloUniverse.svelte +185 -0
- package/src/ruvocal/src/lib/components/RufloHelpModal.svelte +411 -0
- package/src/ruvocal/src/lib/components/ScrollToBottomBtn.svelte +47 -0
- package/src/ruvocal/src/lib/components/ScrollToPreviousBtn.svelte +77 -0
- package/src/ruvocal/src/lib/components/ShareConversationModal.svelte +182 -0
- package/src/ruvocal/src/lib/components/StopGeneratingBtn.svelte +69 -0
- package/src/ruvocal/src/lib/components/SubscribeModal.svelte +87 -0
- package/src/ruvocal/src/lib/components/Switch.svelte +36 -0
- package/src/ruvocal/src/lib/components/SystemPromptModal.svelte +44 -0
- package/src/ruvocal/src/lib/components/Toast.svelte +27 -0
- package/src/ruvocal/src/lib/components/Tooltip.svelte +30 -0
- package/src/ruvocal/src/lib/components/WelcomeModal.svelte +46 -0
- package/src/ruvocal/src/lib/components/chat/Alternatives.svelte +77 -0
- package/src/ruvocal/src/lib/components/chat/BlockWrapper.svelte +72 -0
- package/src/ruvocal/src/lib/components/chat/ChatInput.svelte +490 -0
- package/src/ruvocal/src/lib/components/chat/ChatIntroduction.svelte +123 -0
- package/src/ruvocal/src/lib/components/chat/ChatMessage.svelte +548 -0
- package/src/ruvocal/src/lib/components/chat/ChatWindow.svelte +1057 -0
- package/src/ruvocal/src/lib/components/chat/FileDropzone.svelte +92 -0
- package/src/ruvocal/src/lib/components/chat/ImageLightbox.svelte +66 -0
- package/src/ruvocal/src/lib/components/chat/MarkdownBlock.svelte +23 -0
- package/src/ruvocal/src/lib/components/chat/MarkdownRenderer.svelte +69 -0
- package/src/ruvocal/src/lib/components/chat/MarkdownRenderer.svelte.test.ts +58 -0
- package/src/ruvocal/src/lib/components/chat/MessageAvatar.svelte +103 -0
- package/src/ruvocal/src/lib/components/chat/ModelSwitch.svelte +64 -0
- package/src/ruvocal/src/lib/components/chat/OpenReasoningResults.svelte +81 -0
- package/src/ruvocal/src/lib/components/chat/TaskGroup.svelte +88 -0
- package/src/ruvocal/src/lib/components/chat/ToolUpdate.svelte +273 -0
- package/src/ruvocal/src/lib/components/chat/UploadedFile.svelte +253 -0
- package/src/ruvocal/src/lib/components/chat/UrlFetchModal.svelte +203 -0
- package/src/ruvocal/src/lib/components/chat/VoiceRecorder.svelte +214 -0
- package/src/ruvocal/src/lib/components/icons/IconBurger.svelte +20 -0
- package/src/ruvocal/src/lib/components/icons/IconCheap.svelte +20 -0
- package/src/ruvocal/src/lib/components/icons/IconChevron.svelte +24 -0
- package/src/ruvocal/src/lib/components/icons/IconDazzled.svelte +40 -0
- package/src/ruvocal/src/lib/components/icons/IconFast.svelte +20 -0
- package/src/ruvocal/src/lib/components/icons/IconLoading.svelte +22 -0
- package/src/ruvocal/src/lib/components/icons/IconMCP.svelte +28 -0
- package/src/ruvocal/src/lib/components/icons/IconMoon.svelte +21 -0
- package/src/ruvocal/src/lib/components/icons/IconNew.svelte +20 -0
- package/src/ruvocal/src/lib/components/icons/IconOmni.svelte +90 -0
- package/src/ruvocal/src/lib/components/icons/IconPaperclip.svelte +24 -0
- package/src/ruvocal/src/lib/components/icons/IconPro.svelte +37 -0
- package/src/ruvocal/src/lib/components/icons/IconShare.svelte +21 -0
- package/src/ruvocal/src/lib/components/icons/IconSun.svelte +93 -0
- package/src/ruvocal/src/lib/components/icons/Logo.svelte +68 -0
- package/src/ruvocal/src/lib/components/icons/LogoHuggingFaceBorderless.svelte +54 -0
- package/src/ruvocal/src/lib/components/mcp/AddServerForm.svelte +250 -0
- package/src/ruvocal/src/lib/components/mcp/MCPServerManager.svelte +185 -0
- package/src/ruvocal/src/lib/components/mcp/ServerCard.svelte +203 -0
- package/src/ruvocal/src/lib/components/players/AudioPlayer.svelte +82 -0
- package/src/ruvocal/src/lib/components/voice/AudioWaveform.svelte +96 -0
- package/src/ruvocal/src/lib/components/wasm/GalleryPanel.svelte +357 -0
- package/src/ruvocal/src/lib/constants/mcpExamples.ts +114 -0
- package/src/ruvocal/src/lib/constants/mime.ts +11 -0
- package/src/ruvocal/src/lib/constants/pagination.ts +1 -0
- package/src/ruvocal/src/lib/constants/publicSepToken.ts +1 -0
- package/src/ruvocal/src/lib/constants/routerExamples.ts +133 -0
- package/src/ruvocal/src/lib/constants/rvagentPresets.ts +206 -0
- package/src/ruvocal/src/lib/createShareLink.ts +27 -0
- package/src/ruvocal/src/lib/jobs/refresh-conversation-stats.ts +297 -0
- package/src/ruvocal/src/lib/migrations/lock.ts +56 -0
- package/src/ruvocal/src/lib/migrations/migrations.spec.ts +74 -0
- package/src/ruvocal/src/lib/migrations/migrations.ts +109 -0
- package/src/ruvocal/src/lib/migrations/routines/01-update-search-assistants.ts +50 -0
- package/src/ruvocal/src/lib/migrations/routines/02-update-assistants-models.ts +48 -0
- package/src/ruvocal/src/lib/migrations/routines/04-update-message-updates.ts +151 -0
- package/src/ruvocal/src/lib/migrations/routines/05-update-message-files.ts +56 -0
- package/src/ruvocal/src/lib/migrations/routines/06-trim-message-updates.ts +56 -0
- package/src/ruvocal/src/lib/migrations/routines/08-update-featured-to-review.ts +32 -0
- package/src/ruvocal/src/lib/migrations/routines/09-delete-empty-conversations.spec.ts +214 -0
- package/src/ruvocal/src/lib/migrations/routines/09-delete-empty-conversations.ts +88 -0
- package/src/ruvocal/src/lib/migrations/routines/10-update-reports-assistantid.ts +29 -0
- package/src/ruvocal/src/lib/migrations/routines/index.ts +15 -0
- package/src/ruvocal/src/lib/server/__tests__/conversation-stop-generating.spec.ts +103 -0
- package/src/ruvocal/src/lib/server/abortRegistry.ts +57 -0
- package/src/ruvocal/src/lib/server/abortedGenerations.ts +43 -0
- package/src/ruvocal/src/lib/server/adminToken.ts +62 -0
- package/src/ruvocal/src/lib/server/api/__tests__/conversations-id.spec.ts +296 -0
- package/src/ruvocal/src/lib/server/api/__tests__/conversations-message.spec.ts +216 -0
- package/src/ruvocal/src/lib/server/api/__tests__/conversations.spec.ts +235 -0
- package/src/ruvocal/src/lib/server/api/__tests__/misc.spec.ts +72 -0
- package/src/ruvocal/src/lib/server/api/__tests__/testHelpers.ts +86 -0
- package/src/ruvocal/src/lib/server/api/__tests__/user-reports.spec.ts +78 -0
- package/src/ruvocal/src/lib/server/api/__tests__/user.spec.ts +239 -0
- package/src/ruvocal/src/lib/server/api/types.ts +37 -0
- package/src/ruvocal/src/lib/server/api/utils/requireAuth.ts +22 -0
- package/src/ruvocal/src/lib/server/api/utils/resolveConversation.ts +69 -0
- package/src/ruvocal/src/lib/server/api/utils/resolveModel.ts +27 -0
- package/src/ruvocal/src/lib/server/api/utils/superjsonResponse.ts +15 -0
- package/src/ruvocal/src/lib/server/apiToken.ts +11 -0
- package/src/ruvocal/src/lib/server/auth.ts +554 -0
- package/src/ruvocal/src/lib/server/config.ts +187 -0
- package/src/ruvocal/src/lib/server/conversation.ts +83 -0
- package/src/ruvocal/src/lib/server/database/__tests__/rvf.spec.ts +709 -0
- package/src/ruvocal/src/lib/server/database/postgres.ts +700 -0
- package/src/ruvocal/src/lib/server/database/rvf.ts +1078 -0
- package/src/ruvocal/src/lib/server/database.ts +145 -0
- package/src/ruvocal/src/lib/server/endpoints/document.ts +68 -0
- package/src/ruvocal/src/lib/server/endpoints/endpoints.ts +43 -0
- package/src/ruvocal/src/lib/server/endpoints/images.ts +211 -0
- package/src/ruvocal/src/lib/server/endpoints/openai/endpointOai.ts +266 -0
- package/src/ruvocal/src/lib/server/endpoints/openai/openAIChatToTextGenerationStream.ts +212 -0
- package/src/ruvocal/src/lib/server/endpoints/openai/openAICompletionToTextGenerationStream.ts +32 -0
- package/src/ruvocal/src/lib/server/endpoints/preprocessMessages.ts +61 -0
- package/src/ruvocal/src/lib/server/exitHandler.ts +59 -0
- package/src/ruvocal/src/lib/server/files/downloadFile.ts +34 -0
- package/src/ruvocal/src/lib/server/files/uploadFile.ts +29 -0
- package/src/ruvocal/src/lib/server/findRepoRoot.ts +13 -0
- package/src/ruvocal/src/lib/server/fonts/Inter-Black.ttf +0 -0
- package/src/ruvocal/src/lib/server/fonts/Inter-Bold.ttf +0 -0
- package/src/ruvocal/src/lib/server/fonts/Inter-ExtraBold.ttf +0 -0
- package/src/ruvocal/src/lib/server/fonts/Inter-ExtraLight.ttf +0 -0
- package/src/ruvocal/src/lib/server/fonts/Inter-Light.ttf +0 -0
- package/src/ruvocal/src/lib/server/fonts/Inter-Medium.ttf +0 -0
- package/src/ruvocal/src/lib/server/fonts/Inter-Regular.ttf +0 -0
- package/src/ruvocal/src/lib/server/fonts/Inter-SemiBold.ttf +0 -0
- package/src/ruvocal/src/lib/server/fonts/Inter-Thin.ttf +0 -0
- package/src/ruvocal/src/lib/server/generateFromDefaultEndpoint.ts +46 -0
- package/src/ruvocal/src/lib/server/hooks/error.ts +37 -0
- package/src/ruvocal/src/lib/server/hooks/fetch.ts +22 -0
- package/src/ruvocal/src/lib/server/hooks/handle.ts +250 -0
- package/src/ruvocal/src/lib/server/hooks/init.ts +51 -0
- package/src/ruvocal/src/lib/server/isURLLocal.spec.ts +31 -0
- package/src/ruvocal/src/lib/server/isURLLocal.ts +74 -0
- package/src/ruvocal/src/lib/server/logger.ts +42 -0
- package/src/ruvocal/src/lib/server/mcp/clientPool.spec.ts +175 -0
- package/src/ruvocal/src/lib/server/mcp/clientPool.ts +0 -0
- package/src/ruvocal/src/lib/server/mcp/hf.ts +32 -0
- package/src/ruvocal/src/lib/server/mcp/httpClient.ts +122 -0
- package/src/ruvocal/src/lib/server/mcp/registry.ts +76 -0
- package/src/ruvocal/src/lib/server/mcp/tools.ts +196 -0
- package/src/ruvocal/src/lib/server/metrics.ts +255 -0
- package/src/ruvocal/src/lib/server/models.ts +518 -0
- package/src/ruvocal/src/lib/server/requestContext.ts +55 -0
- package/src/ruvocal/src/lib/server/router/arch.ts +230 -0
- package/src/ruvocal/src/lib/server/router/endpoint.ts +316 -0
- package/src/ruvocal/src/lib/server/router/multimodal.ts +28 -0
- package/src/ruvocal/src/lib/server/router/policy.ts +49 -0
- package/src/ruvocal/src/lib/server/router/toolsRoute.ts +51 -0
- package/src/ruvocal/src/lib/server/router/types.ts +21 -0
- package/src/ruvocal/src/lib/server/sendSlack.ts +23 -0
- package/src/ruvocal/src/lib/server/textGeneration/generate.ts +258 -0
- package/src/ruvocal/src/lib/server/textGeneration/index.ts +96 -0
- package/src/ruvocal/src/lib/server/textGeneration/mcp/fileRefs.ts +155 -0
- package/src/ruvocal/src/lib/server/textGeneration/mcp/routerResolution.ts +108 -0
- package/src/ruvocal/src/lib/server/textGeneration/mcp/runMcpFlow.ts +831 -0
- package/src/ruvocal/src/lib/server/textGeneration/mcp/toolInvocation.ts +349 -0
- package/src/ruvocal/src/lib/server/textGeneration/mcp/wasmTools.test.ts +633 -0
- package/src/ruvocal/src/lib/server/textGeneration/reasoning.ts +23 -0
- package/src/ruvocal/src/lib/server/textGeneration/title.ts +83 -0
- package/src/ruvocal/src/lib/server/textGeneration/types.ts +28 -0
- package/src/ruvocal/src/lib/server/textGeneration/utils/prepareFiles.ts +88 -0
- package/src/ruvocal/src/lib/server/textGeneration/utils/routing.ts +21 -0
- package/src/ruvocal/src/lib/server/textGeneration/utils/toolPrompt.ts +49 -0
- package/src/ruvocal/src/lib/server/urlSafety.ts +77 -0
- package/src/ruvocal/src/lib/server/usageLimits.ts +30 -0
- package/src/ruvocal/src/lib/stores/autopilotStore.svelte.ts +175 -0
- package/src/ruvocal/src/lib/stores/backgroundGenerations.svelte.ts +32 -0
- package/src/ruvocal/src/lib/stores/backgroundGenerations.ts +1 -0
- package/src/ruvocal/src/lib/stores/errors.ts +9 -0
- package/src/ruvocal/src/lib/stores/isAborted.ts +3 -0
- package/src/ruvocal/src/lib/stores/isPro.ts +4 -0
- package/src/ruvocal/src/lib/stores/loading.ts +3 -0
- package/src/ruvocal/src/lib/stores/mcpServers.ts +534 -0
- package/src/ruvocal/src/lib/stores/pendingChatInput.ts +3 -0
- package/src/ruvocal/src/lib/stores/pendingMessage.ts +9 -0
- package/src/ruvocal/src/lib/stores/settings.ts +182 -0
- package/src/ruvocal/src/lib/stores/shareModal.ts +13 -0
- package/src/ruvocal/src/lib/stores/titleUpdate.ts +8 -0
- package/src/ruvocal/src/lib/stores/wasmMcp.ts +472 -0
- package/src/ruvocal/src/lib/switchTheme.ts +124 -0
- package/src/ruvocal/src/lib/types/AbortedGeneration.ts +8 -0
- package/src/ruvocal/src/lib/types/Assistant.ts +31 -0
- package/src/ruvocal/src/lib/types/AssistantStats.ts +11 -0
- package/src/ruvocal/src/lib/types/ConfigKey.ts +4 -0
- package/src/ruvocal/src/lib/types/ConvSidebar.ts +9 -0
- package/src/ruvocal/src/lib/types/Conversation.ts +27 -0
- package/src/ruvocal/src/lib/types/ConversationStats.ts +13 -0
- package/src/ruvocal/src/lib/types/Message.ts +41 -0
- package/src/ruvocal/src/lib/types/MessageEvent.ts +10 -0
- package/src/ruvocal/src/lib/types/MessageUpdate.ts +139 -0
- package/src/ruvocal/src/lib/types/MigrationResult.ts +7 -0
- package/src/ruvocal/src/lib/types/Model.ts +23 -0
- package/src/ruvocal/src/lib/types/Report.ts +12 -0
- package/src/ruvocal/src/lib/types/Review.ts +6 -0
- package/src/ruvocal/src/lib/types/Semaphore.ts +19 -0
- package/src/ruvocal/src/lib/types/Session.ts +22 -0
- package/src/ruvocal/src/lib/types/Settings.ts +93 -0
- package/src/ruvocal/src/lib/types/SharedConversation.ts +9 -0
- package/src/ruvocal/src/lib/types/Template.ts +6 -0
- package/src/ruvocal/src/lib/types/Timestamps.ts +4 -0
- package/src/ruvocal/src/lib/types/TokenCache.ts +6 -0
- package/src/ruvocal/src/lib/types/Tool.ts +77 -0
- package/src/ruvocal/src/lib/types/UrlDependency.ts +5 -0
- package/src/ruvocal/src/lib/types/User.ts +14 -0
- package/src/ruvocal/src/lib/utils/PublicConfig.svelte.ts +75 -0
- package/src/ruvocal/src/lib/utils/auth.ts +17 -0
- package/src/ruvocal/src/lib/utils/chunk.ts +33 -0
- package/src/ruvocal/src/lib/utils/cookiesAreEnabled.ts +13 -0
- package/src/ruvocal/src/lib/utils/debounce.ts +17 -0
- package/src/ruvocal/src/lib/utils/deepestChild.ts +6 -0
- package/src/ruvocal/src/lib/utils/favicon.ts +21 -0
- package/src/ruvocal/src/lib/utils/fetchJSON.ts +23 -0
- package/src/ruvocal/src/lib/utils/file2base64.ts +14 -0
- package/src/ruvocal/src/lib/utils/formatUserCount.ts +37 -0
- package/src/ruvocal/src/lib/utils/generationState.spec.ts +75 -0
- package/src/ruvocal/src/lib/utils/generationState.ts +26 -0
- package/src/ruvocal/src/lib/utils/getHref.ts +41 -0
- package/src/ruvocal/src/lib/utils/getReturnFromGenerator.ts +7 -0
- package/src/ruvocal/src/lib/utils/haptics.ts +64 -0
- package/src/ruvocal/src/lib/utils/hashConv.ts +12 -0
- package/src/ruvocal/src/lib/utils/hf.ts +17 -0
- package/src/ruvocal/src/lib/utils/isDesktop.ts +7 -0
- package/src/ruvocal/src/lib/utils/isUrl.ts +8 -0
- package/src/ruvocal/src/lib/utils/isVirtualKeyboard.ts +16 -0
- package/src/ruvocal/src/lib/utils/loadAttachmentsFromUrls.ts +115 -0
- package/src/ruvocal/src/lib/utils/marked.spec.ts +96 -0
- package/src/ruvocal/src/lib/utils/marked.ts +531 -0
- package/src/ruvocal/src/lib/utils/mcpValidation.ts +147 -0
- package/src/ruvocal/src/lib/utils/mergeAsyncGenerators.ts +38 -0
- package/src/ruvocal/src/lib/utils/messageUpdates.spec.ts +262 -0
- package/src/ruvocal/src/lib/utils/messageUpdates.ts +324 -0
- package/src/ruvocal/src/lib/utils/mime.ts +56 -0
- package/src/ruvocal/src/lib/utils/models.ts +14 -0
- package/src/ruvocal/src/lib/utils/parseBlocks.ts +120 -0
- package/src/ruvocal/src/lib/utils/parseIncompleteMarkdown.ts +644 -0
- package/src/ruvocal/src/lib/utils/parseStringToList.ts +10 -0
- package/src/ruvocal/src/lib/utils/randomUuid.ts +14 -0
- package/src/ruvocal/src/lib/utils/searchTokens.ts +33 -0
- package/src/ruvocal/src/lib/utils/sha256.ts +7 -0
- package/src/ruvocal/src/lib/utils/stringifyError.ts +12 -0
- package/src/ruvocal/src/lib/utils/sum.ts +3 -0
- package/src/ruvocal/src/lib/utils/template.spec.ts +59 -0
- package/src/ruvocal/src/lib/utils/template.ts +53 -0
- package/src/ruvocal/src/lib/utils/timeout.ts +9 -0
- package/src/ruvocal/src/lib/utils/toolProgress.spec.ts +46 -0
- package/src/ruvocal/src/lib/utils/toolProgress.ts +11 -0
- package/src/ruvocal/src/lib/utils/tree/addChildren.spec.ts +102 -0
- package/src/ruvocal/src/lib/utils/tree/addChildren.ts +48 -0
- package/src/ruvocal/src/lib/utils/tree/addSibling.spec.ts +81 -0
- package/src/ruvocal/src/lib/utils/tree/addSibling.ts +41 -0
- package/src/ruvocal/src/lib/utils/tree/buildSubtree.spec.ts +110 -0
- package/src/ruvocal/src/lib/utils/tree/buildSubtree.ts +24 -0
- package/src/ruvocal/src/lib/utils/tree/convertLegacyConversation.spec.ts +31 -0
- package/src/ruvocal/src/lib/utils/tree/convertLegacyConversation.ts +36 -0
- package/src/ruvocal/src/lib/utils/tree/isMessageId.spec.ts +15 -0
- package/src/ruvocal/src/lib/utils/tree/isMessageId.ts +5 -0
- package/src/ruvocal/src/lib/utils/tree/tree.d.ts +14 -0
- package/src/ruvocal/src/lib/utils/tree/treeHelpers.spec.ts +167 -0
- package/src/ruvocal/src/lib/utils/updates.ts +39 -0
- package/src/ruvocal/src/lib/utils/urlParams.ts +13 -0
- package/src/ruvocal/src/lib/wasm/idb.ts +438 -0
- package/src/ruvocal/src/lib/wasm/index.ts +1213 -0
- package/src/ruvocal/src/lib/wasm/tests/wasm-capabilities.test.ts +565 -0
- package/src/ruvocal/src/lib/wasm/wasm.worker.ts +332 -0
- package/src/ruvocal/src/lib/wasm/workerClient.ts +166 -0
- package/src/ruvocal/src/lib/workers/autopilotWorker.ts +221 -0
- package/src/ruvocal/src/lib/workers/detailFetchWorker.ts +100 -0
- package/src/ruvocal/src/lib/workers/markdownWorker.ts +61 -0
- package/src/ruvocal/src/routes/+error.svelte +20 -0
- package/src/ruvocal/src/routes/+layout.svelte +324 -0
- package/src/ruvocal/src/routes/+layout.ts +91 -0
- package/src/ruvocal/src/routes/+page.svelte +168 -0
- package/src/ruvocal/src/routes/.well-known/oauth-cimd/+server.ts +37 -0
- package/src/ruvocal/src/routes/__debug/openai/+server.ts +21 -0
- package/src/ruvocal/src/routes/admin/export/+server.ts +159 -0
- package/src/ruvocal/src/routes/admin/stats/compute/+server.ts +16 -0
- package/src/ruvocal/src/routes/api/conversation/[id]/+server.ts +40 -0
- package/src/ruvocal/src/routes/api/conversation/[id]/message/[messageId]/+server.ts +42 -0
- package/src/ruvocal/src/routes/api/conversations/+server.ts +48 -0
- package/src/ruvocal/src/routes/api/fetch-url/+server.ts +147 -0
- package/src/ruvocal/src/routes/api/mcp/health/+server.ts +292 -0
- package/src/ruvocal/src/routes/api/mcp/servers/+server.ts +32 -0
- package/src/ruvocal/src/routes/api/models/+server.ts +25 -0
- package/src/ruvocal/src/routes/api/transcribe/+server.ts +104 -0
- package/src/ruvocal/src/routes/api/user/+server.ts +15 -0
- package/src/ruvocal/src/routes/api/user/validate-token/+server.ts +20 -0
- package/src/ruvocal/src/routes/api/v2/conversations/+server.ts +48 -0
- package/src/ruvocal/src/routes/api/v2/conversations/[id]/+server.ts +94 -0
- package/src/ruvocal/src/routes/api/v2/conversations/[id]/message/[messageId]/+server.ts +43 -0
- package/src/ruvocal/src/routes/api/v2/conversations/import-share/+server.ts +23 -0
- package/src/ruvocal/src/routes/api/v2/debug/config/+server.ts +16 -0
- package/src/ruvocal/src/routes/api/v2/debug/refresh/+server.ts +30 -0
- package/src/ruvocal/src/routes/api/v2/export/+server.ts +196 -0
- package/src/ruvocal/src/routes/api/v2/feature-flags/+server.ts +14 -0
- package/src/ruvocal/src/routes/api/v2/models/+server.ts +38 -0
- package/src/ruvocal/src/routes/api/v2/models/[namespace]/+server.ts +8 -0
- package/src/ruvocal/src/routes/api/v2/models/[namespace]/[model]/+server.ts +8 -0
- package/src/ruvocal/src/routes/api/v2/models/[namespace]/[model]/subscribe/+server.ts +28 -0
- package/src/ruvocal/src/routes/api/v2/models/[namespace]/subscribe/+server.ts +28 -0
- package/src/ruvocal/src/routes/api/v2/models/old/+server.ts +7 -0
- package/src/ruvocal/src/routes/api/v2/models/refresh/+server.ts +33 -0
- package/src/ruvocal/src/routes/api/v2/public-config/+server.ts +7 -0
- package/src/ruvocal/src/routes/api/v2/user/+server.ts +17 -0
- package/src/ruvocal/src/routes/api/v2/user/billing-orgs/+server.ts +73 -0
- package/src/ruvocal/src/routes/api/v2/user/reports/+server.ts +17 -0
- package/src/ruvocal/src/routes/api/v2/user/settings/+server.ts +110 -0
- package/src/ruvocal/src/routes/conversation/+server.ts +115 -0
- package/src/ruvocal/src/routes/conversation/[id]/+page.svelte +586 -0
- package/src/ruvocal/src/routes/conversation/[id]/+page.ts +60 -0
- package/src/ruvocal/src/routes/conversation/[id]/+server.ts +740 -0
- package/src/ruvocal/src/routes/conversation/[id]/message/[messageId]/prompt/+server.ts +66 -0
- package/src/ruvocal/src/routes/conversation/[id]/share/+server.ts +69 -0
- package/src/ruvocal/src/routes/conversation/[id]/stop-generating/+server.ts +35 -0
- package/src/ruvocal/src/routes/healthcheck/+server.ts +3 -0
- package/src/ruvocal/src/routes/login/+server.ts +5 -0
- package/src/ruvocal/src/routes/login/callback/+server.ts +103 -0
- package/src/ruvocal/src/routes/login/callback/updateUser.spec.ts +157 -0
- package/src/ruvocal/src/routes/login/callback/updateUser.ts +215 -0
- package/src/ruvocal/src/routes/logout/+server.ts +18 -0
- package/src/ruvocal/src/routes/metrics/+server.ts +18 -0
- package/src/ruvocal/src/routes/models/+page.svelte +233 -0
- package/src/ruvocal/src/routes/models/[...model]/+page.svelte +161 -0
- package/src/ruvocal/src/routes/models/[...model]/+page.ts +14 -0
- package/src/ruvocal/src/routes/models/[...model]/thumbnail.png/+server.ts +64 -0
- package/src/ruvocal/src/routes/models/[...model]/thumbnail.png/ModelThumbnail.svelte +28 -0
- package/src/ruvocal/src/routes/privacy/+page.svelte +11 -0
- package/src/ruvocal/src/routes/r/[id]/+page.ts +34 -0
- package/src/ruvocal/src/routes/settings/(nav)/+layout.svelte +282 -0
- package/src/ruvocal/src/routes/settings/(nav)/+layout.ts +1 -0
- package/src/ruvocal/src/routes/settings/(nav)/+page.svelte +0 -0
- package/src/ruvocal/src/routes/settings/(nav)/+server.ts +59 -0
- package/src/ruvocal/src/routes/settings/(nav)/[...model]/+page.svelte +464 -0
- package/src/ruvocal/src/routes/settings/(nav)/[...model]/+page.ts +14 -0
- package/src/ruvocal/src/routes/settings/(nav)/application/+page.svelte +362 -0
- package/src/ruvocal/src/routes/settings/+layout.svelte +40 -0
- package/src/ruvocal/src/styles/highlight-js.css +195 -0
- package/src/ruvocal/src/styles/main.css +144 -0
- package/src/ruvocal/static/chatui/apple-touch-icon.png +0 -0
- package/src/ruvocal/static/chatui/favicon-dark.svg +3 -0
- package/src/ruvocal/static/chatui/favicon-dev.svg +3 -0
- package/src/ruvocal/static/chatui/favicon.ico +0 -0
- package/src/ruvocal/static/chatui/favicon.svg +3 -0
- package/src/ruvocal/static/chatui/icon-128x128.png +0 -0
- package/src/ruvocal/static/chatui/icon-144x144.png +0 -0
- package/src/ruvocal/static/chatui/icon-192x192.png +0 -0
- package/src/ruvocal/static/chatui/icon-256x256.png +0 -0
- package/src/ruvocal/static/chatui/icon-36x36.png +0 -0
- package/src/ruvocal/static/chatui/icon-48x48.png +0 -0
- package/src/ruvocal/static/chatui/icon-512x512.png +0 -0
- package/src/ruvocal/static/chatui/icon-72x72.png +0 -0
- package/src/ruvocal/static/chatui/icon-96x96.png +0 -0
- package/src/ruvocal/static/chatui/icon.svg +3 -0
- package/src/ruvocal/static/chatui/logo.svg +7 -0
- package/src/ruvocal/static/chatui/manifest.json +54 -0
- package/src/ruvocal/static/chatui/omni-welcome.gif +0 -0
- package/src/ruvocal/static/chatui/omni-welcome.png +0 -0
- package/src/ruvocal/static/chatui/welcome.js +184 -0
- package/src/ruvocal/static/chatui/welcome.svg +1 -0
- package/src/ruvocal/static/huggingchat/apple-touch-icon.png +0 -0
- package/src/ruvocal/static/huggingchat/assistants-thumbnail.png +0 -0
- package/src/ruvocal/static/huggingchat/castle-example.jpg +0 -0
- package/src/ruvocal/static/huggingchat/favicon-dark.svg +4 -0
- package/src/ruvocal/static/huggingchat/favicon-dev.svg +4 -0
- package/src/ruvocal/static/huggingchat/favicon.ico +0 -0
- package/src/ruvocal/static/huggingchat/favicon.svg +4 -0
- package/src/ruvocal/static/huggingchat/fulltext-logo.svg +2 -0
- package/src/ruvocal/static/huggingchat/icon-128x128.png +0 -0
- package/src/ruvocal/static/huggingchat/icon-144x144.png +0 -0
- package/src/ruvocal/static/huggingchat/icon-192x192.png +0 -0
- package/src/ruvocal/static/huggingchat/icon-256x256.png +0 -0
- package/src/ruvocal/static/huggingchat/icon-36x36.png +0 -0
- package/src/ruvocal/static/huggingchat/icon-48x48.png +0 -0
- package/src/ruvocal/static/huggingchat/icon-512x512.png +0 -0
- package/src/ruvocal/static/huggingchat/icon-72x72.png +0 -0
- package/src/ruvocal/static/huggingchat/icon-96x96.png +0 -0
- package/src/ruvocal/static/huggingchat/icon.svg +4 -0
- package/src/ruvocal/static/huggingchat/logo.svg +4 -0
- package/src/ruvocal/static/huggingchat/manifest.json +54 -0
- package/src/ruvocal/static/huggingchat/omni-welcome.gif +0 -0
- package/src/ruvocal/static/huggingchat/routes.chat.json +226 -0
- package/src/ruvocal/static/huggingchat/thumbnail.png +0 -0
- package/src/ruvocal/static/huggingchat/tools-thumbnail.png +0 -0
- package/src/ruvocal/static/robots.txt +10 -0
- package/src/ruvocal/static/wasm/rvagent_wasm.js +1539 -0
- package/src/ruvocal/static/wasm/rvagent_wasm_bg.wasm +0 -0
- package/src/ruvocal/stub/@reflink/reflink/index.js +0 -0
- package/src/ruvocal/stub/@reflink/reflink/package.json +5 -0
- package/src/ruvocal/svelte.config.js +53 -0
- package/src/ruvocal/tailwind.config.cjs +30 -0
- package/src/ruvocal/tsconfig.json +19 -0
- package/src/ruvocal/vite.config.ts +87 -0
- package/src/scripts/deploy.sh +116 -0
- package/src/scripts/generate-config.js +245 -0
- package/src/scripts/generate-welcome.js +187 -0
- 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
|