@fraziym/axoniz 0.1.0
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/LICENSE +21 -0
- package/README.md +76 -0
- package/dist/agents/definition.d.ts +15 -0
- package/dist/agents/definition.d.ts.map +1 -0
- package/dist/agents/definition.js +41 -0
- package/dist/agents/definition.js.map +1 -0
- package/dist/agents/index.d.ts +12 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +11 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/registry.d.ts +17 -0
- package/dist/agents/registry.d.ts.map +1 -0
- package/dist/agents/registry.js +23 -0
- package/dist/agents/registry.js.map +1 -0
- package/dist/agents/specialized.d.ts +31 -0
- package/dist/agents/specialized.d.ts.map +1 -0
- package/dist/agents/specialized.js +75 -0
- package/dist/agents/specialized.js.map +1 -0
- package/dist/awareness/index.d.ts +14 -0
- package/dist/awareness/index.d.ts.map +1 -0
- package/dist/awareness/index.js +22 -0
- package/dist/awareness/index.js.map +1 -0
- package/dist/awareness/service.d.ts +180 -0
- package/dist/awareness/service.d.ts.map +1 -0
- package/dist/awareness/service.js +789 -0
- package/dist/awareness/service.js.map +1 -0
- package/dist/cli/entry.d.ts +13 -0
- package/dist/cli/entry.d.ts.map +1 -0
- package/dist/cli/entry.js +35 -0
- package/dist/cli/entry.js.map +1 -0
- package/dist/comms/telegram.d.ts +41 -0
- package/dist/comms/telegram.d.ts.map +1 -0
- package/dist/comms/telegram.js +332 -0
- package/dist/comms/telegram.js.map +1 -0
- package/dist/core/agent.d.ts +252 -0
- package/dist/core/agent.d.ts.map +1 -0
- package/dist/core/agent.js +1399 -0
- package/dist/core/agent.js.map +1 -0
- package/dist/core/auth.d.ts +23 -0
- package/dist/core/auth.d.ts.map +1 -0
- package/dist/core/auth.js +113 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/authority.d.ts +118 -0
- package/dist/core/authority.d.ts.map +1 -0
- package/dist/core/authority.js +402 -0
- package/dist/core/authority.js.map +1 -0
- package/dist/core/backend/async_queue.d.ts +17 -0
- package/dist/core/backend/async_queue.d.ts.map +1 -0
- package/dist/core/backend/async_queue.js +66 -0
- package/dist/core/backend/async_queue.js.map +1 -0
- package/dist/core/backend/index.d.ts +24 -0
- package/dist/core/backend/index.d.ts.map +1 -0
- package/dist/core/backend/index.js +128 -0
- package/dist/core/backend/index.js.map +1 -0
- package/dist/core/backend/llama_server.d.ts +39 -0
- package/dist/core/backend/llama_server.d.ts.map +1 -0
- package/dist/core/backend/llama_server.js +182 -0
- package/dist/core/backend/llama_server.js.map +1 -0
- package/dist/core/backend/llamacpp.d.ts +40 -0
- package/dist/core/backend/llamacpp.d.ts.map +1 -0
- package/dist/core/backend/llamacpp.js +200 -0
- package/dist/core/backend/llamacpp.js.map +1 -0
- package/dist/core/backend/lmstudio.d.ts +53 -0
- package/dist/core/backend/lmstudio.d.ts.map +1 -0
- package/dist/core/backend/lmstudio.js +274 -0
- package/dist/core/backend/lmstudio.js.map +1 -0
- package/dist/core/backend/ollama.d.ts +25 -0
- package/dist/core/backend/ollama.d.ts.map +1 -0
- package/dist/core/backend/ollama.js +86 -0
- package/dist/core/backend/ollama.js.map +1 -0
- package/dist/core/backend/openai.d.ts +27 -0
- package/dist/core/backend/openai.d.ts.map +1 -0
- package/dist/core/backend/openai.js +84 -0
- package/dist/core/backend/openai.js.map +1 -0
- package/dist/core/backend/types.d.ts +73 -0
- package/dist/core/backend/types.d.ts.map +1 -0
- package/dist/core/backend/types.js +137 -0
- package/dist/core/backend/types.js.map +1 -0
- package/dist/core/chat_store.d.ts +77 -0
- package/dist/core/chat_store.d.ts.map +1 -0
- package/dist/core/chat_store.js +332 -0
- package/dist/core/chat_store.js.map +1 -0
- package/dist/core/cli.d.ts +82 -0
- package/dist/core/cli.d.ts.map +1 -0
- package/dist/core/cli.js +602 -0
- package/dist/core/cli.js.map +1 -0
- package/dist/core/config.d.ts +127 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +295 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/debug.d.ts +8 -0
- package/dist/core/debug.d.ts.map +1 -0
- package/dist/core/debug.js +8 -0
- package/dist/core/debug.js.map +1 -0
- package/dist/core/downloader.d.ts +31 -0
- package/dist/core/downloader.d.ts.map +1 -0
- package/dist/core/downloader.js +180 -0
- package/dist/core/downloader.js.map +1 -0
- package/dist/core/errors.d.ts +80 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +134 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/extras.d.ts +160 -0
- package/dist/core/extras.d.ts.map +1 -0
- package/dist/core/extras.js +553 -0
- package/dist/core/extras.js.map +1 -0
- package/dist/core/health.d.ts +27 -0
- package/dist/core/health.d.ts.map +1 -0
- package/dist/core/health.js +96 -0
- package/dist/core/health.js.map +1 -0
- package/dist/core/history.d.ts +50 -0
- package/dist/core/history.d.ts.map +1 -0
- package/dist/core/history.js +276 -0
- package/dist/core/history.js.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +6 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/intelligence/ast_index.d.ts +255 -0
- package/dist/core/intelligence/ast_index.d.ts.map +1 -0
- package/dist/core/intelligence/ast_index.js +1369 -0
- package/dist/core/intelligence/ast_index.js.map +1 -0
- package/dist/core/intelligence/confidence.d.ts +113 -0
- package/dist/core/intelligence/confidence.d.ts.map +1 -0
- package/dist/core/intelligence/confidence.js +298 -0
- package/dist/core/intelligence/confidence.js.map +1 -0
- package/dist/core/intelligence/context_compressor.d.ts +80 -0
- package/dist/core/intelligence/context_compressor.d.ts.map +1 -0
- package/dist/core/intelligence/context_compressor.js +245 -0
- package/dist/core/intelligence/context_compressor.js.map +1 -0
- package/dist/core/intelligence/daemon.d.ts +247 -0
- package/dist/core/intelligence/daemon.d.ts.map +1 -0
- package/dist/core/intelligence/daemon.js +842 -0
- package/dist/core/intelligence/daemon.js.map +1 -0
- package/dist/core/intelligence/index.d.ts +60 -0
- package/dist/core/intelligence/index.d.ts.map +1 -0
- package/dist/core/intelligence/index.js +101 -0
- package/dist/core/intelligence/index.js.map +1 -0
- package/dist/core/intelligence/predictor.d.ts +246 -0
- package/dist/core/intelligence/predictor.d.ts.map +1 -0
- package/dist/core/intelligence/predictor.js +590 -0
- package/dist/core/intelligence/predictor.js.map +1 -0
- package/dist/core/intelligence/reflex.d.ts +84 -0
- package/dist/core/intelligence/reflex.d.ts.map +1 -0
- package/dist/core/intelligence/reflex.js +344 -0
- package/dist/core/intelligence/reflex.js.map +1 -0
- package/dist/core/intelligence/self_correction.d.ts +121 -0
- package/dist/core/intelligence/self_correction.d.ts.map +1 -0
- package/dist/core/intelligence/self_correction.js +327 -0
- package/dist/core/intelligence/self_correction.js.map +1 -0
- package/dist/core/intelligence/skill_distiller.d.ts +100 -0
- package/dist/core/intelligence/skill_distiller.d.ts.map +1 -0
- package/dist/core/intelligence/skill_distiller.js +246 -0
- package/dist/core/intelligence/skill_distiller.js.map +1 -0
- package/dist/core/intelligence/swarm.d.ts +330 -0
- package/dist/core/intelligence/swarm.d.ts.map +1 -0
- package/dist/core/intelligence/swarm.js +1064 -0
- package/dist/core/intelligence/swarm.js.map +1 -0
- package/dist/core/intelligence/trajectory.d.ts +107 -0
- package/dist/core/intelligence/trajectory.d.ts.map +1 -0
- package/dist/core/intelligence/trajectory.js +330 -0
- package/dist/core/intelligence/trajectory.js.map +1 -0
- package/dist/core/logger.d.ts +111 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +640 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/loop.d.ts +123 -0
- package/dist/core/loop.d.ts.map +1 -0
- package/dist/core/loop.js +423 -0
- package/dist/core/loop.js.map +1 -0
- package/dist/core/memory.d.ts +81 -0
- package/dist/core/memory.d.ts.map +1 -0
- package/dist/core/memory.js +328 -0
- package/dist/core/memory.js.map +1 -0
- package/dist/core/metrics.d.ts +29 -0
- package/dist/core/metrics.d.ts.map +1 -0
- package/dist/core/metrics.js +109 -0
- package/dist/core/metrics.js.map +1 -0
- package/dist/core/models.d.ts +51 -0
- package/dist/core/models.d.ts.map +1 -0
- package/dist/core/models.js +146 -0
- package/dist/core/models.js.map +1 -0
- package/dist/core/optimizer.d.ts +34 -0
- package/dist/core/optimizer.d.ts.map +1 -0
- package/dist/core/optimizer.js +131 -0
- package/dist/core/optimizer.js.map +1 -0
- package/dist/core/persona.d.ts +35 -0
- package/dist/core/persona.d.ts.map +1 -0
- package/dist/core/persona.js +126 -0
- package/dist/core/persona.js.map +1 -0
- package/dist/core/rate_limit.d.ts +23 -0
- package/dist/core/rate_limit.d.ts.map +1 -0
- package/dist/core/rate_limit.js +62 -0
- package/dist/core/rate_limit.js.map +1 -0
- package/dist/core/runner.d.ts +63 -0
- package/dist/core/runner.d.ts.map +1 -0
- package/dist/core/runner.js +671 -0
- package/dist/core/runner.js.map +1 -0
- package/dist/core/stream_parser.d.ts +43 -0
- package/dist/core/stream_parser.d.ts.map +1 -0
- package/dist/core/stream_parser.js +140 -0
- package/dist/core/stream_parser.js.map +1 -0
- package/dist/core/tool_schemas.d.ts +26 -0
- package/dist/core/tool_schemas.d.ts.map +1 -0
- package/dist/core/tool_schemas.js +124 -0
- package/dist/core/tool_schemas.js.map +1 -0
- package/dist/core/which.d.ts +2 -0
- package/dist/core/which.d.ts.map +1 -0
- package/dist/core/which.js +35 -0
- package/dist/core/which.js.map +1 -0
- package/dist/goals/index.d.ts +9 -0
- package/dist/goals/index.d.ts.map +1 -0
- package/dist/goals/index.js +17 -0
- package/dist/goals/index.js.map +1 -0
- package/dist/goals/service.d.ts +115 -0
- package/dist/goals/service.d.ts.map +1 -0
- package/dist/goals/service.js +415 -0
- package/dist/goals/service.js.map +1 -0
- package/dist/goals/types.d.ts +47 -0
- package/dist/goals/types.d.ts.map +1 -0
- package/dist/goals/types.js +66 -0
- package/dist/goals/types.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/autonomous_startup.d.ts +51 -0
- package/dist/integrations/autonomous_startup.d.ts.map +1 -0
- package/dist/integrations/autonomous_startup.js +419 -0
- package/dist/integrations/autonomous_startup.js.map +1 -0
- package/dist/integrations/goose.d.ts +105 -0
- package/dist/integrations/goose.d.ts.map +1 -0
- package/dist/integrations/goose.js +408 -0
- package/dist/integrations/goose.js.map +1 -0
- package/dist/integrations/index.d.ts +21 -0
- package/dist/integrations/index.d.ts.map +1 -0
- package/dist/integrations/index.js +17 -0
- package/dist/integrations/index.js.map +1 -0
- package/dist/integrations/mcp.d.ts +73 -0
- package/dist/integrations/mcp.d.ts.map +1 -0
- package/dist/integrations/mcp.js +307 -0
- package/dist/integrations/mcp.js.map +1 -0
- package/dist/integrations/mempalace.d.ts +18 -0
- package/dist/integrations/mempalace.d.ts.map +1 -0
- package/dist/integrations/mempalace.js +25 -0
- package/dist/integrations/mempalace.js.map +1 -0
- package/dist/integrations/multi_agent.d.ts +82 -0
- package/dist/integrations/multi_agent.d.ts.map +1 -0
- package/dist/integrations/multi_agent.js +280 -0
- package/dist/integrations/multi_agent.js.map +1 -0
- package/dist/integrations/unified_memory.d.ts +415 -0
- package/dist/integrations/unified_memory.d.ts.map +1 -0
- package/dist/integrations/unified_memory.js +1489 -0
- package/dist/integrations/unified_memory.js.map +1 -0
- package/dist/roles/beru.yaml +48 -0
- package/dist/sidecar/client.d.ts +63 -0
- package/dist/sidecar/client.d.ts.map +1 -0
- package/dist/sidecar/client.js +222 -0
- package/dist/sidecar/client.js.map +1 -0
- package/dist/sidecar/index.d.ts +7 -0
- package/dist/sidecar/index.d.ts.map +1 -0
- package/dist/sidecar/index.js +6 -0
- package/dist/sidecar/index.js.map +1 -0
- package/dist/startup.d.ts +22 -0
- package/dist/startup.d.ts.map +1 -0
- package/dist/startup.js +194 -0
- package/dist/startup.js.map +1 -0
- package/dist/tools/_internal.d.ts +90 -0
- package/dist/tools/_internal.d.ts.map +1 -0
- package/dist/tools/_internal.js +375 -0
- package/dist/tools/_internal.js.map +1 -0
- package/dist/tools/_smoke.d.ts +2 -0
- package/dist/tools/_smoke.d.ts.map +1 -0
- package/dist/tools/_smoke.js +203 -0
- package/dist/tools/_smoke.js.map +1 -0
- package/dist/tools/axodex_tools.d.ts +51 -0
- package/dist/tools/axodex_tools.d.ts.map +1 -0
- package/dist/tools/axodex_tools.js +181 -0
- package/dist/tools/axodex_tools.js.map +1 -0
- package/dist/tools/code_tools.d.ts +33 -0
- package/dist/tools/code_tools.d.ts.map +1 -0
- package/dist/tools/code_tools.js +191 -0
- package/dist/tools/code_tools.js.map +1 -0
- package/dist/tools/computer_tools.d.ts +59 -0
- package/dist/tools/computer_tools.d.ts.map +1 -0
- package/dist/tools/computer_tools.js +348 -0
- package/dist/tools/computer_tools.js.map +1 -0
- package/dist/tools/file_tools.d.ts +51 -0
- package/dist/tools/file_tools.d.ts.map +1 -0
- package/dist/tools/file_tools.js +424 -0
- package/dist/tools/file_tools.js.map +1 -0
- package/dist/tools/index.d.ts +13 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +13 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/shell_tools.d.ts +39 -0
- package/dist/tools/shell_tools.d.ts.map +1 -0
- package/dist/tools/shell_tools.js +163 -0
- package/dist/tools/shell_tools.js.map +1 -0
- package/dist/tools/web_tools.d.ts +31 -0
- package/dist/tools/web_tools.d.ts.map +1 -0
- package/dist/tools/web_tools.js +117 -0
- package/dist/tools/web_tools.js.map +1 -0
- package/dist/voice/index.d.ts +41 -0
- package/dist/voice/index.d.ts.map +1 -0
- package/dist/voice/index.js +58 -0
- package/dist/voice/index.js.map +1 -0
- package/dist/voice/light_daemon.d.ts +197 -0
- package/dist/voice/light_daemon.d.ts.map +1 -0
- package/dist/voice/light_daemon.js +746 -0
- package/dist/voice/light_daemon.js.map +1 -0
- package/dist/voice/setup.d.ts +63 -0
- package/dist/voice/setup.d.ts.map +1 -0
- package/dist/voice/setup.js +443 -0
- package/dist/voice/setup.js.map +1 -0
- package/dist/voice/stt.d.ts +148 -0
- package/dist/voice/stt.d.ts.map +1 -0
- package/dist/voice/stt.js +361 -0
- package/dist/voice/stt.js.map +1 -0
- package/dist/voice/tts.d.ts +168 -0
- package/dist/voice/tts.d.ts.map +1 -0
- package/dist/voice/tts.js +698 -0
- package/dist/voice/tts.js.map +1 -0
- package/dist/voice/voice_loop.d.ts +101 -0
- package/dist/voice/voice_loop.d.ts.map +1 -0
- package/dist/voice/voice_loop.js +463 -0
- package/dist/voice/voice_loop.js.map +1 -0
- package/dist/voice/wake_word.d.ts +153 -0
- package/dist/voice/wake_word.d.ts.map +1 -0
- package/dist/voice/wake_word.js +458 -0
- package/dist/voice/wake_word.js.map +1 -0
- package/dist/voice/wav.d.ts +99 -0
- package/dist/voice/wav.d.ts.map +1 -0
- package/dist/voice/wav.js +306 -0
- package/dist/voice/wav.js.map +1 -0
- package/dist/web/broker.d.ts +33 -0
- package/dist/web/broker.d.ts.map +1 -0
- package/dist/web/broker.js +58 -0
- package/dist/web/broker.js.map +1 -0
- package/dist/web/open_browser.d.ts +2 -0
- package/dist/web/open_browser.d.ts.map +1 -0
- package/dist/web/open_browser.js +42 -0
- package/dist/web/open_browser.js.map +1 -0
- package/dist/web/server.d.ts +54 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +831 -0
- package/dist/web/server.js.map +1 -0
- package/dist/web/static/app.js +4121 -0
- package/dist/web/static/index.html +2281 -0
- package/dist/web/static/js/MODULE_SUMMARY.md +124 -0
- package/dist/web/static/js/a11y.js +165 -0
- package/dist/web/static/js/admin.js +2145 -0
- package/dist/web/static/js/assistant.js +475 -0
- package/dist/web/static/js/calendar/reminders.js +114 -0
- package/dist/web/static/js/calendar/utils.js +166 -0
- package/dist/web/static/js/calendar.js +3348 -0
- package/dist/web/static/js/censor.js +356 -0
- package/dist/web/static/js/chat.js +4546 -0
- package/dist/web/static/js/chatRenderer.js +2294 -0
- package/dist/web/static/js/chatStream.js +276 -0
- package/dist/web/static/js/codeRunner.js +398 -0
- package/dist/web/static/js/color/hex.js +14 -0
- package/dist/web/static/js/colorPicker.js +453 -0
- package/dist/web/static/js/compare/icons.js +77 -0
- package/dist/web/static/js/compare/index.js +1482 -0
- package/dist/web/static/js/compare/models.js +104 -0
- package/dist/web/static/js/compare/panes.js +817 -0
- package/dist/web/static/js/compare/probe.js +78 -0
- package/dist/web/static/js/compare/scoreboard.js +223 -0
- package/dist/web/static/js/compare/selector.js +1335 -0
- package/dist/web/static/js/compare/state.js +58 -0
- package/dist/web/static/js/compare/stream.js +695 -0
- package/dist/web/static/js/compare/vote.js +254 -0
- package/dist/web/static/js/cookbook-diagnosis.js +694 -0
- package/dist/web/static/js/cookbook-hwfit.js +1888 -0
- package/dist/web/static/js/cookbook.js +2093 -0
- package/dist/web/static/js/cookbookDownload.js +633 -0
- package/dist/web/static/js/cookbookProgressSignal.js +29 -0
- package/dist/web/static/js/cookbookRunning.js +3506 -0
- package/dist/web/static/js/cookbookServe.js +2128 -0
- package/dist/web/static/js/document.js +9731 -0
- package/dist/web/static/js/documentLibrary.js +3376 -0
- package/dist/web/static/js/dragSort.js +265 -0
- package/dist/web/static/js/editor/ai-inpaint.js +374 -0
- package/dist/web/static/js/editor/ai-models.js +273 -0
- package/dist/web/static/js/editor/ai-rembg.js +230 -0
- package/dist/web/static/js/editor/ai-tool-runner.js +147 -0
- package/dist/web/static/js/editor/ai-tools-misc.js +216 -0
- package/dist/web/static/js/editor/build/controls.js +366 -0
- package/dist/web/static/js/editor/build/popups.js +112 -0
- package/dist/web/static/js/editor/build/right-panel.js +200 -0
- package/dist/web/static/js/editor/build/toolbar.js +73 -0
- package/dist/web/static/js/editor/build/topbar.js +131 -0
- package/dist/web/static/js/editor/build/transform-popup.js +109 -0
- package/dist/web/static/js/editor/canvas-coords.js +21 -0
- package/dist/web/static/js/editor/canvas-events.js +197 -0
- package/dist/web/static/js/editor/canvas-transforms.js +132 -0
- package/dist/web/static/js/editor/checkerboard.js +24 -0
- package/dist/web/static/js/editor/clipboard-and-drop.js +133 -0
- package/dist/web/static/js/editor/composite-helpers.js +83 -0
- package/dist/web/static/js/editor/filters/blur.js +118 -0
- package/dist/web/static/js/editor/filters/edge-feather.js +70 -0
- package/dist/web/static/js/editor/fx/adj-popup.js +677 -0
- package/dist/web/static/js/editor/fx/filter-string.js +38 -0
- package/dist/web/static/js/editor/fx/histogram.js +67 -0
- package/dist/web/static/js/editor/fx/pixel-pass.js +249 -0
- package/dist/web/static/js/editor/harmonize-masks.js +116 -0
- package/dist/web/static/js/editor/history-panel.js +176 -0
- package/dist/web/static/js/editor/keyboard-shortcuts.js +271 -0
- package/dist/web/static/js/editor/layer-helpers.js +131 -0
- package/dist/web/static/js/editor/layer-panel.js +600 -0
- package/dist/web/static/js/editor/mask-utils.js +91 -0
- package/dist/web/static/js/editor/shortcuts-popover.js +149 -0
- package/dist/web/static/js/editor/slider-ux.js +191 -0
- package/dist/web/static/js/editor/snap.js +107 -0
- package/dist/web/static/js/editor/state.js +257 -0
- package/dist/web/static/js/editor/stroke-pipeline.js +162 -0
- package/dist/web/static/js/editor/stroke-tool-sliders.js +64 -0
- package/dist/web/static/js/editor/tools/clone.js +81 -0
- package/dist/web/static/js/editor/tools/crop.js +137 -0
- package/dist/web/static/js/editor/tools/flood-fill.js +72 -0
- package/dist/web/static/js/editor/tools/lasso-mask.js +171 -0
- package/dist/web/static/js/editor/tools/lasso.js +65 -0
- package/dist/web/static/js/editor/tools/move.js +79 -0
- package/dist/web/static/js/editor/tools/stroke.js +123 -0
- package/dist/web/static/js/editor/tools/transform-drag.js +174 -0
- package/dist/web/static/js/editor/tools/transform-handles.js +271 -0
- package/dist/web/static/js/editor/tools/transform-session.js +381 -0
- package/dist/web/static/js/editor/tools/wand.js +46 -0
- package/dist/web/static/js/editor/wire-import.js +148 -0
- package/dist/web/static/js/editor/wire-inpaint-controls.js +164 -0
- package/dist/web/static/js/editor/wire-merge-buttons.js +103 -0
- package/dist/web/static/js/editor/wire-selection-controls.js +170 -0
- package/dist/web/static/js/editor/wire-topbar-menus.js +174 -0
- package/dist/web/static/js/editor/wire-topbar-overflow.js +49 -0
- package/dist/web/static/js/editor/wire-topbar.js +188 -0
- package/dist/web/static/js/emailInbox.js +1245 -0
- package/dist/web/static/js/emailLibrary/replyRecipients.js +27 -0
- package/dist/web/static/js/emailLibrary/signatureFold.js +339 -0
- package/dist/web/static/js/emailLibrary/state.js +34 -0
- package/dist/web/static/js/emailLibrary/utils.js +202 -0
- package/dist/web/static/js/emailLibrary.js +5217 -0
- package/dist/web/static/js/emojiPicker.js +311 -0
- package/dist/web/static/js/escMenuStack.js +102 -0
- package/dist/web/static/js/fileHandler.js +295 -0
- package/dist/web/static/js/gallery.js +2835 -0
- package/dist/web/static/js/galleryEditor.js +3798 -0
- package/dist/web/static/js/group.js +955 -0
- package/dist/web/static/js/init.js +405 -0
- package/dist/web/static/js/keyboard-shortcuts.js +292 -0
- package/dist/web/static/js/langIcons.js +187 -0
- package/dist/web/static/js/markdown/tableRow.js +19 -0
- package/dist/web/static/js/markdown.js +806 -0
- package/dist/web/static/js/memory.js +1418 -0
- package/dist/web/static/js/modalManager.js +1552 -0
- package/dist/web/static/js/modalSnap.js +782 -0
- package/dist/web/static/js/model/matchKey.js +19 -0
- package/dist/web/static/js/modelPicker.js +695 -0
- package/dist/web/static/js/modelSort.js +33 -0
- package/dist/web/static/js/models.js +642 -0
- package/dist/web/static/js/notes.js +5054 -0
- package/dist/web/static/js/package.json +1 -0
- package/dist/web/static/js/platform.js +47 -0
- package/dist/web/static/js/presets.js +1110 -0
- package/dist/web/static/js/providers.js +93 -0
- package/dist/web/static/js/rag.js +177 -0
- package/dist/web/static/js/research/jobs.js +335 -0
- package/dist/web/static/js/research/panel.js +1233 -0
- package/dist/web/static/js/researchSynapse.js +225 -0
- package/dist/web/static/js/search-chat.js +201 -0
- package/dist/web/static/js/search.js +52 -0
- package/dist/web/static/js/section-management.js +260 -0
- package/dist/web/static/js/sessions.js +3108 -0
- package/dist/web/static/js/settings.js +4991 -0
- package/dist/web/static/js/sidebar-layout.js +573 -0
- package/dist/web/static/js/signature.js +504 -0
- package/dist/web/static/js/skills.js +1908 -0
- package/dist/web/static/js/slashAutocomplete.js +267 -0
- package/dist/web/static/js/slashCommands.js +6099 -0
- package/dist/web/static/js/spinner.js +406 -0
- package/dist/web/static/js/storage.js +123 -0
- package/dist/web/static/js/tasks.js +2699 -0
- package/dist/web/static/js/theme.js +2086 -0
- package/dist/web/static/js/tileManager.js +386 -0
- package/dist/web/static/js/tourAutoplay.js +133 -0
- package/dist/web/static/js/tourHints.js +179 -0
- package/dist/web/static/js/tts-ai.js +521 -0
- package/dist/web/static/js/ui.js +1288 -0
- package/dist/web/static/js/util/ordinal.js +13 -0
- package/dist/web/static/js/voiceRecorder.js +283 -0
- package/dist/web/static/js/windowDrag.js +326 -0
- package/dist/web/static/js/windowResize.js +233 -0
- package/dist/web/static/landing.html +824 -0
- package/dist/web/static/lib/docx.umd.min.js +20116 -0
- package/dist/web/static/lib/highlight.min.js +1213 -0
- package/dist/web/static/lib/html2pdf.bundle.min.js +3 -0
- package/dist/web/static/lib/mammoth.browser.min.js +21 -0
- package/dist/web/static/lib/qrcode.min.js +8 -0
- package/dist/web/static/lib/xlsx.full.min.js +24 -0
- package/dist/web/static/login.html +582 -0
- package/dist/web/static/manifest.json +15 -0
- package/dist/web/static/style.css +35733 -0
- package/dist/web/static/sw.js +144 -0
- package/dist/workflows/engine.d.ts +162 -0
- package/dist/workflows/engine.d.ts.map +1 -0
- package/dist/workflows/engine.js +625 -0
- package/dist/workflows/engine.js.map +1 -0
- package/dist/workflows/index.d.ts +8 -0
- package/dist/workflows/index.d.ts.map +1 -0
- package/dist/workflows/index.js +14 -0
- package/dist/workflows/index.js.map +1 -0
- package/package.json +112 -0
|
@@ -0,0 +1,1064 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* axoniz.core.intelligence.swarm
|
|
3
|
+
* ================================
|
|
4
|
+
* Dynamic Domain-Expert Swarm — Sequential Model Swapping
|
|
5
|
+
*
|
|
6
|
+
* The Swarm decomposes any complex task into phases and assigns a
|
|
7
|
+
* dedicated, domain-expert model for each phase:
|
|
8
|
+
*
|
|
9
|
+
* Decomposer → breaks the task into atomic sub-tasks (reasoning model)
|
|
10
|
+
* Worker → executes each sub-task using tools (coding/execution model)
|
|
11
|
+
* Critic → validates each result before accepting (debugging model)
|
|
12
|
+
* Merger → synthesises all results into a final answer (language model)
|
|
13
|
+
*
|
|
14
|
+
* Models are loaded sequentially and unloaded from RAM between phases
|
|
15
|
+
* to stay within the memory constraints of the host machine.
|
|
16
|
+
* Model paths are configured in ~/.axoniz/config.json under `swarm_models`.
|
|
17
|
+
* If no swarm models are configured, the active main model is used for all
|
|
18
|
+
* phases (standard behaviour).
|
|
19
|
+
*
|
|
20
|
+
* Architecture:
|
|
21
|
+
* SwarmOrchestrator → drives phase transitions + model hot-swapping
|
|
22
|
+
* SwarmWorker → executes a single sub-task with tool access
|
|
23
|
+
* SwarmCritic → scores + validates a worker result
|
|
24
|
+
* SwarmMerger → combines all valid results
|
|
25
|
+
*
|
|
26
|
+
* Port notes (Python → Node):
|
|
27
|
+
* - `ThreadPoolExecutor`/`Future` → an async scheduler with an explicit
|
|
28
|
+
* concurrency ceiling (`max_workers`), `Promise.race` for completion and a
|
|
29
|
+
* `Promise.allSettled` for teardown. No worker_threads: every step is I/O
|
|
30
|
+
* bound (LLM streaming / tools), so threads would buy nothing.
|
|
31
|
+
* - `threading.Lock` around model swapping → an async `Mutex` (the swap
|
|
32
|
+
* sequence itself awaits `unload_llm`/`ensure_llm`).
|
|
33
|
+
* - `self.llm.stream_text(messages)` (a sync generator) → `collectStream()`,
|
|
34
|
+
* an async collector that accepts an async generator, a sync generator, a
|
|
35
|
+
* plain string, a `{ text }` response object or a promise of any of those.
|
|
36
|
+
* - Every method that talks to the LLM is `async`. `review`, `merge`,
|
|
37
|
+
* `_decompose`, `run`, `run_simple` and `execute` therefore return
|
|
38
|
+
* Promises where the Python versions blocked the calling thread.
|
|
39
|
+
* - `dict` → `Map<number, SubTask>` for the `completed` registry (Python used
|
|
40
|
+
* integer keys); `_schedule_and_run(sub_tasks, completed)` takes that Map.
|
|
41
|
+
* - Python's `self.tool_map[name](**args)` (keyword arguments) has no direct
|
|
42
|
+
* JS analogue. `SwarmWorker.tool_args_mode` ("auto" | "object" |
|
|
43
|
+
* "positional") decides how arguments are passed; "auto" inspects the
|
|
44
|
+
* handler's declared arity.
|
|
45
|
+
* - `swarm.py` referenced `os.path`/`os.path.exists` without importing `os`
|
|
46
|
+
* in `_prefetch_relevant_files` and `_make_worker`; the port uses
|
|
47
|
+
* `node:path` / `node:fs` properly (the Python text-only path raised
|
|
48
|
+
* NameError there).
|
|
49
|
+
*/
|
|
50
|
+
import fs from "node:fs";
|
|
51
|
+
import path from "node:path";
|
|
52
|
+
import { debug, info, warn } from "../debug.js";
|
|
53
|
+
import { resolveSwarmModels } from "../config.js";
|
|
54
|
+
/* ─── Small helpers ───────────────────────────────────────────────────────── */
|
|
55
|
+
/** Render an unknown thrown value the way Python's `str(e)` would. */
|
|
56
|
+
export function errText(e) {
|
|
57
|
+
if (e instanceof Error)
|
|
58
|
+
return e.message || e.name;
|
|
59
|
+
if (typeof e === "string")
|
|
60
|
+
return e;
|
|
61
|
+
try {
|
|
62
|
+
return JSON.stringify(e);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return String(e);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function asRecord(v) {
|
|
69
|
+
return v !== null && typeof v === "object" && !Array.isArray(v)
|
|
70
|
+
? v
|
|
71
|
+
: {};
|
|
72
|
+
}
|
|
73
|
+
function toInt(v, fallback) {
|
|
74
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
75
|
+
return Number.isFinite(n) ? Math.trunc(n) : fallback;
|
|
76
|
+
}
|
|
77
|
+
function activeProviderName(cfg) {
|
|
78
|
+
return String(asRecord(cfg.llm).active_provider ?? "llamacpp");
|
|
79
|
+
}
|
|
80
|
+
function currentModelPath(cfg) {
|
|
81
|
+
const llm = asRecord(cfg.llm);
|
|
82
|
+
const providers = asRecord(llm.providers);
|
|
83
|
+
const prov = asRecord(providers[activeProviderName(cfg)]);
|
|
84
|
+
return String(prov.model_path ?? cfg.model_path ?? "");
|
|
85
|
+
}
|
|
86
|
+
function setProviderModelPath(cfg, value) {
|
|
87
|
+
const llm = cfg.llm;
|
|
88
|
+
const providers = asRecord(llm.providers);
|
|
89
|
+
llm.providers = providers;
|
|
90
|
+
const prov = activeProviderName(cfg);
|
|
91
|
+
const entry = asRecord(providers[prov]);
|
|
92
|
+
entry.model_path = value;
|
|
93
|
+
providers[prov] = entry;
|
|
94
|
+
}
|
|
95
|
+
/** Serialises an async critical section (Python's `_MODEL_SWAP_LOCK`). */
|
|
96
|
+
class Mutex {
|
|
97
|
+
_tail = Promise.resolve();
|
|
98
|
+
async runExclusive(fn) {
|
|
99
|
+
const previous = this._tail;
|
|
100
|
+
let release;
|
|
101
|
+
this._tail = new Promise((resolve) => {
|
|
102
|
+
release = resolve;
|
|
103
|
+
});
|
|
104
|
+
await previous;
|
|
105
|
+
try {
|
|
106
|
+
return await fn();
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
release();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Global lock: only one model swap may happen at a time
|
|
114
|
+
const _MODEL_SWAP_LOCK = new Mutex();
|
|
115
|
+
/** Drain any stream-like LLM return value into a single string. */
|
|
116
|
+
export async function collectStream(llm, messages) {
|
|
117
|
+
if (!llm)
|
|
118
|
+
throw new Error("no LLM available");
|
|
119
|
+
const fn = (typeof llm.streamText === "function" && llm.streamText) ||
|
|
120
|
+
(typeof llm.stream_text === "function" && llm.stream_text) ||
|
|
121
|
+
(typeof llm.stream === "function" && llm.stream) ||
|
|
122
|
+
null;
|
|
123
|
+
if (!fn) {
|
|
124
|
+
if (typeof llm.complete === "function") {
|
|
125
|
+
const res = await llm.complete(messages);
|
|
126
|
+
const rec = asRecord(res);
|
|
127
|
+
if (typeof res === "string")
|
|
128
|
+
return res;
|
|
129
|
+
if (typeof rec["text"] === "string")
|
|
130
|
+
return rec["text"];
|
|
131
|
+
return String(res ?? "");
|
|
132
|
+
}
|
|
133
|
+
throw new Error("LLM exposes no stream_text/streamText/complete method");
|
|
134
|
+
}
|
|
135
|
+
const out = await fn.call(llm, messages);
|
|
136
|
+
if (out === null || out === undefined)
|
|
137
|
+
return "";
|
|
138
|
+
if (typeof out === "string")
|
|
139
|
+
return out;
|
|
140
|
+
const asAsync = out;
|
|
141
|
+
if (typeof asAsync[Symbol.asyncIterator] === "function") {
|
|
142
|
+
let acc = "";
|
|
143
|
+
for await (const token of asAsync)
|
|
144
|
+
acc += String(token);
|
|
145
|
+
return acc;
|
|
146
|
+
}
|
|
147
|
+
const asSync = out;
|
|
148
|
+
if (typeof asSync[Symbol.iterator] === "function") {
|
|
149
|
+
let acc = "";
|
|
150
|
+
for (const token of asSync)
|
|
151
|
+
acc += String(token);
|
|
152
|
+
return acc;
|
|
153
|
+
}
|
|
154
|
+
const rec = asRecord(out);
|
|
155
|
+
if (typeof rec["text"] === "string")
|
|
156
|
+
return rec["text"];
|
|
157
|
+
return String(out);
|
|
158
|
+
}
|
|
159
|
+
export class SubTask {
|
|
160
|
+
id;
|
|
161
|
+
description;
|
|
162
|
+
depends_on;
|
|
163
|
+
priority;
|
|
164
|
+
tool_hint;
|
|
165
|
+
verify;
|
|
166
|
+
status; // pending / running / done / failed / rejected
|
|
167
|
+
result;
|
|
168
|
+
score;
|
|
169
|
+
duration_ms;
|
|
170
|
+
worker_id;
|
|
171
|
+
constructor(init, description = "", extra = {}) {
|
|
172
|
+
const o = typeof init === "number" ? { id: init, description, ...extra } : init;
|
|
173
|
+
this.id = o.id;
|
|
174
|
+
this.description = o.description ?? "";
|
|
175
|
+
this.depends_on = [...(o.depends_on ?? [])];
|
|
176
|
+
this.priority = o.priority ?? 5;
|
|
177
|
+
this.tool_hint = o.tool_hint ?? "";
|
|
178
|
+
this.verify = o.verify ?? "";
|
|
179
|
+
this.status = o.status ?? "pending";
|
|
180
|
+
this.result = o.result ?? "";
|
|
181
|
+
this.score = o.score ?? 0.0;
|
|
182
|
+
this.duration_ms = o.duration_ms ?? 0;
|
|
183
|
+
this.worker_id = o.worker_id ?? 0;
|
|
184
|
+
}
|
|
185
|
+
get dependsOn() {
|
|
186
|
+
return this.depends_on;
|
|
187
|
+
}
|
|
188
|
+
get toolHint() {
|
|
189
|
+
return this.tool_hint;
|
|
190
|
+
}
|
|
191
|
+
get durationMs() {
|
|
192
|
+
return this.duration_ms;
|
|
193
|
+
}
|
|
194
|
+
get workerId() {
|
|
195
|
+
return this.worker_id;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
export class SwarmResult {
|
|
199
|
+
task;
|
|
200
|
+
sub_tasks;
|
|
201
|
+
merged;
|
|
202
|
+
success;
|
|
203
|
+
total_ms;
|
|
204
|
+
parallelism;
|
|
205
|
+
constructor(init) {
|
|
206
|
+
this.task = init.task;
|
|
207
|
+
this.sub_tasks = init.sub_tasks;
|
|
208
|
+
this.merged = init.merged;
|
|
209
|
+
this.success = init.success;
|
|
210
|
+
this.total_ms = init.total_ms;
|
|
211
|
+
this.parallelism = init.parallelism;
|
|
212
|
+
}
|
|
213
|
+
get subTasks() {
|
|
214
|
+
return this.sub_tasks;
|
|
215
|
+
}
|
|
216
|
+
get totalMs() {
|
|
217
|
+
return this.total_ms;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/* ─── Decomposer ─────────────────────────────────────────────────────────────── */
|
|
221
|
+
export const DECOMPOSE_PROMPT = `Decompose a task into independent parallel sub-tasks.
|
|
222
|
+
|
|
223
|
+
Rules:
|
|
224
|
+
1. Maximize parallelism — only use depends_on for true sequential dependencies
|
|
225
|
+
2. Each sub-task is atomic and self-contained
|
|
226
|
+
3. Max 8 sub-tasks
|
|
227
|
+
4. tool_hint: primary tool (file_write, shell_run, etc.)
|
|
228
|
+
5. verify: one-line success check
|
|
229
|
+
|
|
230
|
+
Respond ONLY with JSON array:
|
|
231
|
+
[
|
|
232
|
+
{"id": 1, "description": "...", "depends_on": [], "priority": 8, "tool_hint": "file_write", "verify": "..."},
|
|
233
|
+
...
|
|
234
|
+
]`;
|
|
235
|
+
export const CRITIC_PROMPT = `Review a worker's sub-task result.
|
|
236
|
+
Respond ONLY with JSON:
|
|
237
|
+
{"score": 8.5, "pass": true, "issues": [], "fix_hint": ""}
|
|
238
|
+
score: 0-10 (pass if >= 6.0)
|
|
239
|
+
issues: list of problems found
|
|
240
|
+
fix_hint: how to fix if score < 6.0`;
|
|
241
|
+
export const MERGE_PROMPT = `Synthesize multiple parallel worker outputs into one coherent response.
|
|
242
|
+
Be brief and structured. No extra commentary.`;
|
|
243
|
+
export class SwarmWorker {
|
|
244
|
+
id;
|
|
245
|
+
tool_map;
|
|
246
|
+
llm;
|
|
247
|
+
workspace;
|
|
248
|
+
max_steps;
|
|
249
|
+
on_progress;
|
|
250
|
+
tool_capable;
|
|
251
|
+
tool_args_mode;
|
|
252
|
+
constructor(a, b, c, d, e = 8, f = null, g = true) {
|
|
253
|
+
const o = typeof a === "number"
|
|
254
|
+
? {
|
|
255
|
+
worker_id: a,
|
|
256
|
+
tool_map: b ?? {},
|
|
257
|
+
llm: c ?? null,
|
|
258
|
+
workspace: d ?? ".",
|
|
259
|
+
max_steps: e,
|
|
260
|
+
on_progress: f,
|
|
261
|
+
tool_capable: g,
|
|
262
|
+
}
|
|
263
|
+
: a;
|
|
264
|
+
this.id = o.worker_id;
|
|
265
|
+
this.tool_map = o.tool_map ?? {};
|
|
266
|
+
this.llm = o.llm ?? null;
|
|
267
|
+
this.workspace = o.workspace;
|
|
268
|
+
this.max_steps = o.max_steps ?? 8;
|
|
269
|
+
this.on_progress = o.on_progress ?? null;
|
|
270
|
+
this.tool_capable = o.tool_capable ?? true;
|
|
271
|
+
this.tool_args_mode = o.tool_args_mode ?? "auto";
|
|
272
|
+
}
|
|
273
|
+
get workerId() {
|
|
274
|
+
return this.id;
|
|
275
|
+
}
|
|
276
|
+
get maxSteps() {
|
|
277
|
+
return this.max_steps;
|
|
278
|
+
}
|
|
279
|
+
get toolCapable() {
|
|
280
|
+
return this.tool_capable;
|
|
281
|
+
}
|
|
282
|
+
/** Execute a sub-task, update it in-place, return it. */
|
|
283
|
+
async execute(sub_task, context = "") {
|
|
284
|
+
sub_task.status = "running";
|
|
285
|
+
sub_task.worker_id = this.id;
|
|
286
|
+
const t0 = Date.now();
|
|
287
|
+
const prompt = this._build_prompt(sub_task, context);
|
|
288
|
+
// Route to the right execution mode based on model capability
|
|
289
|
+
const result = this.tool_capable
|
|
290
|
+
? await this._run_with_tools(prompt)
|
|
291
|
+
: await this._run_text_only(prompt, sub_task);
|
|
292
|
+
sub_task.result = result;
|
|
293
|
+
sub_task.duration_ms = Date.now() - t0;
|
|
294
|
+
sub_task.status = result && !result.slice(0, 50).includes("[ERROR]") ? "done" : "failed";
|
|
295
|
+
if (this.on_progress) {
|
|
296
|
+
this.on_progress({
|
|
297
|
+
event: "worker_done",
|
|
298
|
+
worker: this.id,
|
|
299
|
+
task_id: sub_task.id,
|
|
300
|
+
status: sub_task.status,
|
|
301
|
+
ms: sub_task.duration_ms,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
return sub_task;
|
|
305
|
+
}
|
|
306
|
+
_build_prompt(sub_task, context) {
|
|
307
|
+
const lines = [
|
|
308
|
+
`PARALLEL WORKER TASK #${sub_task.id}:`,
|
|
309
|
+
`${sub_task.description}`,
|
|
310
|
+
"",
|
|
311
|
+
`Success criterion: ${sub_task.verify || "task complete"}`,
|
|
312
|
+
`Primary tool: ${sub_task.tool_hint || "any"}`,
|
|
313
|
+
];
|
|
314
|
+
if (context) {
|
|
315
|
+
lines.push(`\nCONTEXT FROM OTHER WORKERS:\n${context.slice(0, 800)}`);
|
|
316
|
+
}
|
|
317
|
+
lines.push("\nExecute this task efficiently. " +
|
|
318
|
+
"Use tools to complete it. " +
|
|
319
|
+
"Call done() with a clear summary when finished.");
|
|
320
|
+
return lines.join("\n");
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Prompt for text-only models: pre-fetch relevant file content so the
|
|
324
|
+
* model never needs to call a tool to read the workspace.
|
|
325
|
+
*/
|
|
326
|
+
_build_text_prompt(sub_task, context) {
|
|
327
|
+
// Pre-fetch file content referenced in the task description
|
|
328
|
+
const file_content_block = this._prefetch_relevant_files(sub_task.description);
|
|
329
|
+
const lines = [
|
|
330
|
+
`TASK: ${sub_task.description}`,
|
|
331
|
+
`SUCCESS CRITERION: ${sub_task.verify || "task complete"}`,
|
|
332
|
+
];
|
|
333
|
+
if (file_content_block) {
|
|
334
|
+
lines.push(`\nRELEVANT FILE CONTENT:\n${file_content_block}`);
|
|
335
|
+
}
|
|
336
|
+
if (context) {
|
|
337
|
+
lines.push(`\nCONTEXT FROM OTHER WORKERS:\n${context.slice(0, 600)}`);
|
|
338
|
+
}
|
|
339
|
+
lines.push("\nProvide your complete output below. " +
|
|
340
|
+
"Do NOT attempt to call any tools or functions. " +
|
|
341
|
+
"Write your full answer as plain text or code.");
|
|
342
|
+
return lines.join("\n");
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Extract file paths mentioned in the task description and read them
|
|
346
|
+
* so a text-only model has the content it needs inline.
|
|
347
|
+
*/
|
|
348
|
+
_prefetch_relevant_files(description) {
|
|
349
|
+
// Match common file path patterns in the task description
|
|
350
|
+
const patterns = [
|
|
351
|
+
/[\w./\\-]+\.py/g,
|
|
352
|
+
/[\w./\\-]+\.ts/g,
|
|
353
|
+
/[\w./\\-]+\.js/g,
|
|
354
|
+
/[\w./\\-]+\.json/g,
|
|
355
|
+
/[\w./\\-]+\.yaml/g,
|
|
356
|
+
/[\w./\\-]+\.md/g,
|
|
357
|
+
];
|
|
358
|
+
const found_paths = [];
|
|
359
|
+
for (const pat of patterns) {
|
|
360
|
+
found_paths.push(...description.match(pat) ?? []);
|
|
361
|
+
}
|
|
362
|
+
const blocks = [];
|
|
363
|
+
for (const p of found_paths.slice(0, 3)) {
|
|
364
|
+
// cap at 3 files to stay within context
|
|
365
|
+
const full_path = path.isAbsolute(p) ? p : path.join(this.workspace, p);
|
|
366
|
+
if (fs.existsSync(full_path)) {
|
|
367
|
+
try {
|
|
368
|
+
const content = fs.readFileSync(full_path, "utf-8").slice(0, 3000); // first 3000 chars
|
|
369
|
+
blocks.push(`--- ${p} ---\n${content}`);
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
/* unreadable — skip, mirroring Python's bare except */
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return blocks.join("\n\n");
|
|
377
|
+
}
|
|
378
|
+
/** Parse `<tool>{...}</tool>` fallback calls out of a model reply. */
|
|
379
|
+
_parse_fallback_calls(text) {
|
|
380
|
+
const calls = [];
|
|
381
|
+
for (const m of text.matchAll(/<tool>([\s\S]*?)<\/tool>/g)) {
|
|
382
|
+
const raw = (m[1] ?? "").trim().replace(/,\s*([}\]])/g, "$1");
|
|
383
|
+
try {
|
|
384
|
+
const obj = asRecord(JSON.parse(raw));
|
|
385
|
+
const name = obj["name"] ?? obj["tool"];
|
|
386
|
+
let args = obj["args"] ?? obj["arguments"] ?? {};
|
|
387
|
+
if (typeof args === "string") {
|
|
388
|
+
try {
|
|
389
|
+
args = JSON.parse(args);
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
args = {};
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (name) {
|
|
396
|
+
calls.push({ name: String(name), args: asRecord(args) });
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
catch {
|
|
400
|
+
/* unparseable hallucination — skip */
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return calls;
|
|
404
|
+
}
|
|
405
|
+
_is_done(text) {
|
|
406
|
+
const t = text.toLowerCase();
|
|
407
|
+
return [
|
|
408
|
+
"task complete",
|
|
409
|
+
"task is complete",
|
|
410
|
+
"all done",
|
|
411
|
+
"i have completed",
|
|
412
|
+
"finished.",
|
|
413
|
+
"the task has been",
|
|
414
|
+
"<endofop>",
|
|
415
|
+
"successfully completed",
|
|
416
|
+
].some((s) => t.includes(s));
|
|
417
|
+
}
|
|
418
|
+
/** Invoke a tool handler, mirroring Python's `tool(**args)`. */
|
|
419
|
+
_invoke_tool(name, args) {
|
|
420
|
+
const fn = this.tool_map[name];
|
|
421
|
+
if (!fn)
|
|
422
|
+
throw new Error(`unknown tool '${name}'`);
|
|
423
|
+
const mode = this.tool_args_mode;
|
|
424
|
+
const useObject = mode === "object" || (mode === "auto" && (fn.length <= 1));
|
|
425
|
+
const values = Object.values(args);
|
|
426
|
+
return useObject ? fn(args) : fn(...values);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Tool-capable execution: full tool loop with <tool> parsing.
|
|
430
|
+
* Used for Gemma 1B, Nemotron 4B and any other model that passed
|
|
431
|
+
* tool-calling validation.
|
|
432
|
+
*/
|
|
433
|
+
async _run_with_tools(prompt) {
|
|
434
|
+
const tool_names = Object.keys(this.tool_map)
|
|
435
|
+
.filter((k) => k !== "done")
|
|
436
|
+
.join(", ");
|
|
437
|
+
const tool_fmt = "\n\nTo call a tool:\n" +
|
|
438
|
+
'<tool>{"name": "tool_name", "args": {"param": "value"}}</tool>\n' +
|
|
439
|
+
`Tools: ${tool_names}\n` +
|
|
440
|
+
'Finish: <tool>{"name": "done", "args": {"result": "summary"}}</tool>';
|
|
441
|
+
const messages = [
|
|
442
|
+
{ role: "system", content: "You are a focused parallel worker agent." + tool_fmt },
|
|
443
|
+
{ role: "user", content: prompt },
|
|
444
|
+
];
|
|
445
|
+
let finished = false;
|
|
446
|
+
let final_result = "";
|
|
447
|
+
let nudge = 0;
|
|
448
|
+
for (let step = 0; step < this.max_steps; step++) {
|
|
449
|
+
try {
|
|
450
|
+
const full = await collectStream(this.llm, messages);
|
|
451
|
+
const calls = this._parse_fallback_calls(full);
|
|
452
|
+
if (calls.length > 0) {
|
|
453
|
+
nudge = 0;
|
|
454
|
+
messages.push({ role: "assistant", content: full });
|
|
455
|
+
for (const fc of calls) {
|
|
456
|
+
const name = fc.name;
|
|
457
|
+
const args = fc.args;
|
|
458
|
+
if (name === "done") {
|
|
459
|
+
final_result = String(args["result"] ?? full);
|
|
460
|
+
finished = true;
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
if (name in this.tool_map) {
|
|
464
|
+
let result_str;
|
|
465
|
+
try {
|
|
466
|
+
const r = this._invoke_tool(name, args);
|
|
467
|
+
result_str = r !== null && r !== undefined ? String(r) : "OK";
|
|
468
|
+
}
|
|
469
|
+
catch (e) {
|
|
470
|
+
result_str = `[ERROR] ${errText(e)}`;
|
|
471
|
+
}
|
|
472
|
+
messages.push({
|
|
473
|
+
role: "user",
|
|
474
|
+
content: `Tool '${name}' returned:\n${result_str}\n\nContinue.`,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (finished)
|
|
479
|
+
break;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (this._is_done(full)) {
|
|
483
|
+
final_result = full;
|
|
484
|
+
finished = true;
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
nudge += 1;
|
|
488
|
+
messages.push({ role: "assistant", content: full });
|
|
489
|
+
if (nudge >= 3) {
|
|
490
|
+
final_result = full;
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
messages.push({ role: "user", content: "Use a tool or call done()." });
|
|
494
|
+
}
|
|
495
|
+
catch (e) {
|
|
496
|
+
final_result = `[ERROR] Worker ${this.id}: ${errText(e)}`;
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return final_result || "Worker completed without explicit result.";
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Text-only execution for models that cannot call tools (DeepSeek Coder
|
|
504
|
+
* 1.3B, Qwen2.5 1.5B, Qwen2.5 Coder 1.5B).
|
|
505
|
+
*
|
|
506
|
+
* The model receives file content pre-fetched inline and is instructed
|
|
507
|
+
* to produce pure text/code output only. The caller (orchestrator) is
|
|
508
|
+
* responsible for writing results to disk via a tool-capable worker.
|
|
509
|
+
*/
|
|
510
|
+
async _run_text_only(prompt, sub_task) {
|
|
511
|
+
void prompt; // Python accepted `prompt` and rebuilt a richer one from the sub-task.
|
|
512
|
+
// Build a richer prompt with pre-fetched file content
|
|
513
|
+
const rich_prompt = this._build_text_prompt(sub_task, "");
|
|
514
|
+
const messages = [
|
|
515
|
+
{
|
|
516
|
+
role: "system",
|
|
517
|
+
content: "You are a specialist code/analysis worker. " +
|
|
518
|
+
"You receive a task and any relevant file content inline. " +
|
|
519
|
+
"Produce only the requested output — code, analysis, or summary. " +
|
|
520
|
+
"Do NOT output JSON tool calls, function calls, or <tool> blocks. " +
|
|
521
|
+
"Write plain text or code blocks only.",
|
|
522
|
+
},
|
|
523
|
+
{ role: "user", content: rich_prompt },
|
|
524
|
+
];
|
|
525
|
+
try {
|
|
526
|
+
const result = (await collectStream(this.llm, messages)).trim();
|
|
527
|
+
return result ? result : "[TEXT-WORKER] No output produced.";
|
|
528
|
+
}
|
|
529
|
+
catch (e) {
|
|
530
|
+
return `[ERROR] Text worker ${this.id}: ${errText(e)}`;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Reviews worker results and scores them.
|
|
536
|
+
* Rejects low-quality results and tells the worker what to fix.
|
|
537
|
+
*/
|
|
538
|
+
export class SwarmCritic {
|
|
539
|
+
llm;
|
|
540
|
+
pass_threshold;
|
|
541
|
+
constructor(llm, pass_threshold = 6.0) {
|
|
542
|
+
this.llm = llm;
|
|
543
|
+
this.pass_threshold = pass_threshold;
|
|
544
|
+
}
|
|
545
|
+
get passThreshold() {
|
|
546
|
+
return this.pass_threshold;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Returns {"score": float, "pass": bool, "issues": list, "fix_hint": str}
|
|
550
|
+
* Fast path: obvious failures are caught without LLM.
|
|
551
|
+
*/
|
|
552
|
+
async review(sub_task) {
|
|
553
|
+
// Fast-path checks (no LLM needed)
|
|
554
|
+
if (!sub_task.result || sub_task.result.length < 10) {
|
|
555
|
+
return {
|
|
556
|
+
score: 0.0,
|
|
557
|
+
pass: false,
|
|
558
|
+
issues: ["Empty or trivial result"],
|
|
559
|
+
fix_hint: "Complete the task",
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
if (sub_task.result.startsWith("[ERROR]")) {
|
|
563
|
+
return {
|
|
564
|
+
score: 1.0,
|
|
565
|
+
pass: false,
|
|
566
|
+
issues: [sub_task.result.slice(0, 200)],
|
|
567
|
+
fix_hint: "Fix the error and retry",
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
if (sub_task.status === "failed") {
|
|
571
|
+
return {
|
|
572
|
+
score: 2.0,
|
|
573
|
+
pass: false,
|
|
574
|
+
issues: ["Worker reported failure"],
|
|
575
|
+
fix_hint: "Retry with different approach",
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
// Skip LLM review for simple tasks (performance optimization)
|
|
579
|
+
if (sub_task.priority < 4 || sub_task.result.length < 50) {
|
|
580
|
+
return { score: 7.0, pass: true, issues: [], fix_hint: "" };
|
|
581
|
+
}
|
|
582
|
+
// LLM review for important tasks
|
|
583
|
+
try {
|
|
584
|
+
const msgs = [
|
|
585
|
+
{ role: "system", content: CRITIC_PROMPT },
|
|
586
|
+
{
|
|
587
|
+
role: "user",
|
|
588
|
+
content: `TASK: ${sub_task.description}\n` +
|
|
589
|
+
`VERIFY: ${sub_task.verify}\n` +
|
|
590
|
+
`RESULT:\n${sub_task.result.slice(0, 1000)}`,
|
|
591
|
+
},
|
|
592
|
+
];
|
|
593
|
+
const raw = await collectStream(this.llm, msgs);
|
|
594
|
+
const m = raw.match(/\{[\s\S]*\}/);
|
|
595
|
+
if (m) {
|
|
596
|
+
const obj = asRecord(JSON.parse(m[0]));
|
|
597
|
+
const score = Number(obj["score"] ?? 7.0);
|
|
598
|
+
if (!Number.isFinite(score))
|
|
599
|
+
throw new Error("non-numeric score");
|
|
600
|
+
const issuesRaw = obj["issues"];
|
|
601
|
+
return {
|
|
602
|
+
score,
|
|
603
|
+
pass: score >= this.pass_threshold && Boolean(obj["pass"] ?? true),
|
|
604
|
+
issues: Array.isArray(issuesRaw) ? issuesRaw.map((x) => String(x)) : [],
|
|
605
|
+
fix_hint: String(obj["fix_hint"] ?? ""),
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
catch {
|
|
610
|
+
/* fall through to the neutral default, exactly like Python */
|
|
611
|
+
}
|
|
612
|
+
return { score: 7.0, pass: true, issues: [], fix_hint: "" };
|
|
613
|
+
}
|
|
614
|
+
/** camelCase alias. */
|
|
615
|
+
reviewTask(sub_task) {
|
|
616
|
+
return this.review(sub_task);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
/* ─── Merger ─────────────────────────────────────────────────────────────────── */
|
|
620
|
+
/** Combines all worker results into a coherent final answer. */
|
|
621
|
+
export class SwarmMerger {
|
|
622
|
+
llm;
|
|
623
|
+
constructor(llm) {
|
|
624
|
+
this.llm = llm;
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Merge all passed sub-task results.
|
|
628
|
+
* For simple tasks, does it without LLM (pure concatenation).
|
|
629
|
+
*/
|
|
630
|
+
async merge(task, sub_tasks) {
|
|
631
|
+
const passed = sub_tasks.filter((st) => st.status === "done" && st.score >= 5.0);
|
|
632
|
+
if (passed.length === 0) {
|
|
633
|
+
const failed = sub_tasks.filter((st) => st.status === "failed");
|
|
634
|
+
return (`Swarm completed with issues. ` +
|
|
635
|
+
`${sub_tasks.length} tasks, ${failed.length} failed.\n` +
|
|
636
|
+
sub_tasks.map((st) => ` Task ${st.id}: ${st.result.slice(0, 200)}`).join("\n"));
|
|
637
|
+
}
|
|
638
|
+
// For 1-2 tasks, just concatenate (no LLM overhead)
|
|
639
|
+
if (passed.length <= 2) {
|
|
640
|
+
const parts = passed.map((st) => `Task ${st.id} (${st.description}):\n${st.result}`);
|
|
641
|
+
return parts.join("\n\n---\n\n");
|
|
642
|
+
}
|
|
643
|
+
// For larger swarms, use LLM to merge
|
|
644
|
+
try {
|
|
645
|
+
const parts_text = passed
|
|
646
|
+
.map((st) => `[WORKER ${st.worker_id} | Task ${st.id}: ${st.description}]\n${st.result.slice(0, 600)}`)
|
|
647
|
+
.join("\n\n");
|
|
648
|
+
const msgs = [
|
|
649
|
+
{ role: "system", content: MERGE_PROMPT },
|
|
650
|
+
{ role: "user", content: `ORIGINAL TASK: ${task}\n\nWORKER OUTPUTS:\n${parts_text}` },
|
|
651
|
+
];
|
|
652
|
+
return (await collectStream(this.llm, msgs)).trim();
|
|
653
|
+
}
|
|
654
|
+
catch {
|
|
655
|
+
// Fallback: plain concat
|
|
656
|
+
return passed.map((st) => `• ${st.description}: ${st.result.slice(0, 400)}`).join("\n\n");
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
/** camelCase alias. */
|
|
660
|
+
mergeResults(task, sub_tasks) {
|
|
661
|
+
return this.merge(task, sub_tasks);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
/** Build a name → handler record from a plain object or a JS `Map`. */
|
|
665
|
+
export function toToolMap(value) {
|
|
666
|
+
const out = {};
|
|
667
|
+
if (value instanceof Map) {
|
|
668
|
+
for (const [k, fn] of value) {
|
|
669
|
+
if (typeof fn === "function")
|
|
670
|
+
out[String(k)] = fn;
|
|
671
|
+
}
|
|
672
|
+
return out;
|
|
673
|
+
}
|
|
674
|
+
for (const [k, fn] of Object.entries(asRecord(value))) {
|
|
675
|
+
if (typeof fn === "function")
|
|
676
|
+
out[k] = fn;
|
|
677
|
+
}
|
|
678
|
+
return out;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Main entry point for the swarm system.
|
|
682
|
+
*
|
|
683
|
+
* 1. Decomposes task into parallel sub-tasks
|
|
684
|
+
* 2. Schedules them respecting dependencies
|
|
685
|
+
* 3. Runs workers in a thread pool
|
|
686
|
+
* 4. Critic reviews each result
|
|
687
|
+
* 5. Retries failed/rejected tasks once
|
|
688
|
+
* 6. Merger combines everything
|
|
689
|
+
*
|
|
690
|
+
* Usage:
|
|
691
|
+
* const swarm = new SwarmOrchestrator({ agent, max_workers: 4 });
|
|
692
|
+
* const result = await swarm.run("build a REST API...");
|
|
693
|
+
*/
|
|
694
|
+
export class SwarmOrchestrator {
|
|
695
|
+
agent;
|
|
696
|
+
critic_threshold;
|
|
697
|
+
max_retries;
|
|
698
|
+
on_progress;
|
|
699
|
+
max_workers;
|
|
700
|
+
critic;
|
|
701
|
+
merger;
|
|
702
|
+
_expert_mode;
|
|
703
|
+
_original_model;
|
|
704
|
+
constructor(a, b = 4, c = 6.0, d = 1, e = null) {
|
|
705
|
+
const o = "agent" in a
|
|
706
|
+
? a
|
|
707
|
+
: {
|
|
708
|
+
agent: a,
|
|
709
|
+
max_workers: b,
|
|
710
|
+
critic_threshold: c,
|
|
711
|
+
max_retries: d,
|
|
712
|
+
on_progress: e,
|
|
713
|
+
};
|
|
714
|
+
this.agent = o.agent;
|
|
715
|
+
this.critic_threshold = o.critic_threshold ?? 6.0;
|
|
716
|
+
this.max_retries = o.max_retries ?? 1;
|
|
717
|
+
this.on_progress = o.on_progress ?? null;
|
|
718
|
+
// Resolve any bare filenames in swarm_models to full paths
|
|
719
|
+
resolveSwarmModels(this.agent.config);
|
|
720
|
+
// If domain-expert models are configured, workers must be sequential
|
|
721
|
+
// so we never have two models loaded in RAM at the same time.
|
|
722
|
+
this._expert_mode = Object.values(asRecord(this.agent.config.swarm_models)).some(Boolean);
|
|
723
|
+
this.max_workers = this._expert_mode ? 1 : (o.max_workers ?? 4);
|
|
724
|
+
this.critic = new SwarmCritic(this._llm(), this.critic_threshold);
|
|
725
|
+
this.merger = new SwarmMerger(this._llm());
|
|
726
|
+
// Remember the model that was active before swarm started so we can
|
|
727
|
+
// restore it when the swarm finishes.
|
|
728
|
+
// Support both hierarchical config (llm.providers) and legacy flat config (model_path)
|
|
729
|
+
this._original_model = currentModelPath(this.agent.config);
|
|
730
|
+
}
|
|
731
|
+
/** The LLM/backend the swarm should drive (`llm` or the `backend` alias). */
|
|
732
|
+
_llm() {
|
|
733
|
+
return this.agent.llm ?? this.agent.backend ?? null;
|
|
734
|
+
}
|
|
735
|
+
/** Workspace root used when pre-fetching files for text-only workers. */
|
|
736
|
+
_workspace() {
|
|
737
|
+
return String(this.agent.workspace ?? ".");
|
|
738
|
+
}
|
|
739
|
+
/** `agent.indexer.summary()` when the workspace indexer exposes it. */
|
|
740
|
+
_indexerSummary() {
|
|
741
|
+
const indexer = this.agent.indexer;
|
|
742
|
+
if (!indexer || typeof indexer.summary !== "function")
|
|
743
|
+
return "";
|
|
744
|
+
try {
|
|
745
|
+
return String(indexer.summary() ?? "");
|
|
746
|
+
}
|
|
747
|
+
catch {
|
|
748
|
+
return "";
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
/** The agent's tool registry, whichever spelling it uses. */
|
|
752
|
+
_toolMap() {
|
|
753
|
+
return toToolMap(this.agent._tool_map ?? this.agent.tool_map ?? this.agent.toolMap);
|
|
754
|
+
}
|
|
755
|
+
get expertMode() {
|
|
756
|
+
return this._expert_mode;
|
|
757
|
+
}
|
|
758
|
+
get maxWorkers() {
|
|
759
|
+
return this.max_workers;
|
|
760
|
+
}
|
|
761
|
+
/* ── Model hot-swap (sequential, thread-safe) ─────────────────────────────── */
|
|
762
|
+
async _unloadLlm() {
|
|
763
|
+
try {
|
|
764
|
+
const fn = this.agent.unload_llm ?? this.agent.unloadLlm;
|
|
765
|
+
if (fn)
|
|
766
|
+
await fn.call(this.agent);
|
|
767
|
+
}
|
|
768
|
+
catch (e) {
|
|
769
|
+
debug(`[Swarm] unload_llm failed: ${errText(e)}`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
async _ensureLlm() {
|
|
773
|
+
try {
|
|
774
|
+
const fn = this.agent.ensure_llm ?? this.agent.ensureLlm;
|
|
775
|
+
if (fn)
|
|
776
|
+
await fn.call(this.agent);
|
|
777
|
+
}
|
|
778
|
+
catch (e) {
|
|
779
|
+
warn(`[Swarm] ensure_llm failed: ${errText(e)}`);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Unload the current LLM and load the domain-expert model assigned
|
|
784
|
+
* to `role` in config.swarm_models. A no-op if:
|
|
785
|
+
* - expert mode is disabled (no swarm_models configured)
|
|
786
|
+
* - the requested role has no model assigned
|
|
787
|
+
* - the correct model is already loaded
|
|
788
|
+
*/
|
|
789
|
+
async _swap_model_for_role(role) {
|
|
790
|
+
if (!this._expert_mode) {
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
const cfg = this.agent.config;
|
|
794
|
+
const model_path = String(asRecord(cfg.swarm_models)[role] ?? "");
|
|
795
|
+
if (!model_path) {
|
|
796
|
+
return; // role has no expert model — use whatever is loaded
|
|
797
|
+
}
|
|
798
|
+
if (!fs.existsSync(model_path)) {
|
|
799
|
+
warn(`[Swarm] Model for role '${role}' not found: '${model_path}'`);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
const current_path = currentModelPath(cfg);
|
|
803
|
+
if (current_path === model_path) {
|
|
804
|
+
return; // already the right model
|
|
805
|
+
}
|
|
806
|
+
await _MODEL_SWAP_LOCK.runExclusive(async () => {
|
|
807
|
+
info(`[Swarm] ▶ Phase '${role}' — loading ${path.basename(model_path)}`);
|
|
808
|
+
this._emit("model_swap", { role, model: path.basename(model_path) });
|
|
809
|
+
await this._unloadLlm();
|
|
810
|
+
setProviderModelPath(cfg, model_path);
|
|
811
|
+
await this._ensureLlm();
|
|
812
|
+
// Keep critic and merger wired to the current LLM
|
|
813
|
+
const llm = this._llm();
|
|
814
|
+
this.critic.llm = llm;
|
|
815
|
+
this.merger.llm = llm;
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
/** Reload the model that was active before the swarm started. */
|
|
819
|
+
async _restore_original_model() {
|
|
820
|
+
if (!this._expert_mode || !this._original_model) {
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
const cfg = this.agent.config;
|
|
824
|
+
const current = currentModelPath(cfg);
|
|
825
|
+
if (current === this._original_model) {
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
await _MODEL_SWAP_LOCK.runExclusive(async () => {
|
|
829
|
+
info(`[Swarm] Restoring original model: ${path.basename(this._original_model)}`);
|
|
830
|
+
await this._unloadLlm();
|
|
831
|
+
setProviderModelPath(cfg, this._original_model);
|
|
832
|
+
await this._ensureLlm();
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
/* ── Main entry point ─────────────────────────────────────────────────────── */
|
|
836
|
+
/** Full swarm execution pipeline. Returns SwarmResult. */
|
|
837
|
+
async run(task) {
|
|
838
|
+
const t_start = Date.now();
|
|
839
|
+
const mode = this._expert_mode ? "expert" : "standard";
|
|
840
|
+
info(`[Swarm] Starting (${mode} mode): ${task.slice(0, 60)}`);
|
|
841
|
+
this._emit("swarm_start", { task, mode });
|
|
842
|
+
let all_tasks = [];
|
|
843
|
+
let merged = "";
|
|
844
|
+
try {
|
|
845
|
+
// ── Phase 1: Decompose ──────────────────────────────────────────
|
|
846
|
+
await this._swap_model_for_role("decomposer");
|
|
847
|
+
const sub_tasks = await this._decompose(task);
|
|
848
|
+
info(`[Swarm] Decomposed into ${sub_tasks.length} sub-tasks`);
|
|
849
|
+
this._emit("decomposed", {
|
|
850
|
+
count: sub_tasks.length,
|
|
851
|
+
tasks: sub_tasks.map((st) => st.description),
|
|
852
|
+
});
|
|
853
|
+
// ── Phase 2: Execute ────────────────────────────────────────────
|
|
854
|
+
await this._swap_model_for_role("worker");
|
|
855
|
+
const completed = new Map();
|
|
856
|
+
await this._schedule_and_run(sub_tasks, completed);
|
|
857
|
+
// ── Phase 3: Critic review + retry ──────────────────────────────
|
|
858
|
+
await this._swap_model_for_role("critic");
|
|
859
|
+
for (const st of [...completed.values()]) {
|
|
860
|
+
const review = await this.critic.review(st);
|
|
861
|
+
st.score = review.score;
|
|
862
|
+
if (!review.pass && this.max_retries > 0) {
|
|
863
|
+
info(`[Swarm] Critic rejected task ${st.id} (score=${st.score.toFixed(1)}), retrying`);
|
|
864
|
+
this._emit("critic_retry", {
|
|
865
|
+
task_id: st.id,
|
|
866
|
+
score: st.score,
|
|
867
|
+
issues: review.issues,
|
|
868
|
+
});
|
|
869
|
+
let retry_st = new SubTask({
|
|
870
|
+
id: st.id,
|
|
871
|
+
description: st.description + `\n\nFIX REQUIRED: ${review.fix_hint}`,
|
|
872
|
+
depends_on: st.depends_on,
|
|
873
|
+
priority: st.priority,
|
|
874
|
+
tool_hint: st.tool_hint,
|
|
875
|
+
verify: st.verify,
|
|
876
|
+
});
|
|
877
|
+
await this._swap_model_for_role("worker");
|
|
878
|
+
const worker = this._make_worker(st.id % this.max_workers);
|
|
879
|
+
const ctx = this._build_context(completed, st.id);
|
|
880
|
+
retry_st = await worker.execute(retry_st, ctx);
|
|
881
|
+
await this._swap_model_for_role("critic");
|
|
882
|
+
const review2 = await this.critic.review(retry_st);
|
|
883
|
+
retry_st.score = review2.score;
|
|
884
|
+
completed.set(st.id, retry_st);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
// ── Phase 4: Merge ──────────────────────────────────────────────
|
|
888
|
+
await this._swap_model_for_role("merger");
|
|
889
|
+
all_tasks = [...completed.values()];
|
|
890
|
+
merged = await this.merger.merge(task, all_tasks);
|
|
891
|
+
}
|
|
892
|
+
finally {
|
|
893
|
+
// Always restore the original model so the agent is usable
|
|
894
|
+
// immediately after the swarm finishes, regardless of errors.
|
|
895
|
+
await this._restore_original_model();
|
|
896
|
+
}
|
|
897
|
+
const total_ms = Date.now() - t_start;
|
|
898
|
+
const seq_time_ms = all_tasks.reduce((acc, st) => acc + st.duration_ms, 0);
|
|
899
|
+
const parallelism = seq_time_ms > 0 ? seq_time_ms / Math.max(total_ms, 1) : 1.0;
|
|
900
|
+
const result = new SwarmResult({
|
|
901
|
+
task,
|
|
902
|
+
sub_tasks: all_tasks,
|
|
903
|
+
merged,
|
|
904
|
+
success: all_tasks.some((st) => st.status === "done"),
|
|
905
|
+
total_ms,
|
|
906
|
+
parallelism: Math.round(parallelism * 100) / 100,
|
|
907
|
+
});
|
|
908
|
+
const passed = all_tasks.filter((st) => st.score >= 6.0).length;
|
|
909
|
+
this._emit("swarm_done", {
|
|
910
|
+
total_ms,
|
|
911
|
+
seq_ms: seq_time_ms,
|
|
912
|
+
speedup: `${parallelism.toFixed(1)}x`,
|
|
913
|
+
passed,
|
|
914
|
+
});
|
|
915
|
+
info(`[Swarm] ✓ Done in ${total_ms}ms — ${parallelism.toFixed(1)}x speedup | ${passed}/${all_tasks.length} passed`);
|
|
916
|
+
return result;
|
|
917
|
+
}
|
|
918
|
+
/** Convenience: run and return just the merged string. */
|
|
919
|
+
async run_simple(task) {
|
|
920
|
+
return (await this.run(task)).merged;
|
|
921
|
+
}
|
|
922
|
+
/** camelCase alias. */
|
|
923
|
+
async runSimple(task) {
|
|
924
|
+
return this.run_simple(task);
|
|
925
|
+
}
|
|
926
|
+
/* ── Internal ────────────────────────────────────────────────────────────── */
|
|
927
|
+
/** Use LLM to decompose task into sub-tasks. */
|
|
928
|
+
async _decompose(task) {
|
|
929
|
+
const workspace_summary = this._indexerSummary();
|
|
930
|
+
const context = workspace_summary ? `Workspace: ${workspace_summary}\n\nTask: ${task}` : task;
|
|
931
|
+
const msgs = [
|
|
932
|
+
{ role: "system", content: DECOMPOSE_PROMPT },
|
|
933
|
+
{ role: "user", content: context },
|
|
934
|
+
];
|
|
935
|
+
try {
|
|
936
|
+
const raw = await collectStream(this._llm(), msgs);
|
|
937
|
+
const m = raw.match(/\[[\s\S]*\]/);
|
|
938
|
+
if (m) {
|
|
939
|
+
const data = JSON.parse(m[0]);
|
|
940
|
+
return data.slice(0, 8).map((d, i) => {
|
|
941
|
+
const rec = asRecord(d);
|
|
942
|
+
const depsRaw = rec["depends_on"];
|
|
943
|
+
return new SubTask({
|
|
944
|
+
id: toInt(rec["id"], i + 1),
|
|
945
|
+
description: String(rec["description"] ?? ""),
|
|
946
|
+
depends_on: Array.isArray(depsRaw) ? depsRaw.map((x) => toInt(x, 0)) : [],
|
|
947
|
+
priority: toInt(rec["priority"], 5),
|
|
948
|
+
tool_hint: String(rec["tool_hint"] ?? ""),
|
|
949
|
+
verify: String(rec["verify"] ?? ""),
|
|
950
|
+
});
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
catch (e) {
|
|
955
|
+
warn(`[Swarm] Decompose failed: ${errText(e)}`);
|
|
956
|
+
}
|
|
957
|
+
// Fallback: single sub-task
|
|
958
|
+
return [new SubTask({ id: 1, description: task, verify: "task complete", priority: 8 })];
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Execute sub-tasks respecting depends_on.
|
|
962
|
+
* Independent tasks run in parallel, bounded by `max_workers`
|
|
963
|
+
* (Python's `ThreadPoolExecutor(max_workers=...)`).
|
|
964
|
+
*/
|
|
965
|
+
async _schedule_and_run(sub_tasks, completed) {
|
|
966
|
+
const pending = new Map(sub_tasks.map((st) => [st.id, st]));
|
|
967
|
+
const running = new Map();
|
|
968
|
+
while (pending.size > 0 || running.size > 0) {
|
|
969
|
+
// Find tasks that can now run (all dependencies done)
|
|
970
|
+
const ready = [...pending.values()].filter((st) => st.depends_on.every((dep) => completed.has(dep)));
|
|
971
|
+
let started = false;
|
|
972
|
+
for (const st of ready) {
|
|
973
|
+
if (running.size >= Math.max(this.max_workers, 1))
|
|
974
|
+
break;
|
|
975
|
+
pending.delete(st.id);
|
|
976
|
+
const ctx = this._build_context(completed);
|
|
977
|
+
const worker = this._make_worker(st.id % this.max_workers);
|
|
978
|
+
this._emit("worker_start", { task_id: st.id, desc: st.description });
|
|
979
|
+
const promise = worker
|
|
980
|
+
.execute(st, ctx)
|
|
981
|
+
.then((result_st) => {
|
|
982
|
+
completed.set(result_st.id, result_st);
|
|
983
|
+
})
|
|
984
|
+
.catch((e) => {
|
|
985
|
+
// Mark failed
|
|
986
|
+
const original = sub_tasks.find((s) => s.id === st.id);
|
|
987
|
+
if (original) {
|
|
988
|
+
original.status = "failed";
|
|
989
|
+
original.result = `[ERROR] ${errText(e)}`;
|
|
990
|
+
completed.set(st.id, original);
|
|
991
|
+
}
|
|
992
|
+
})
|
|
993
|
+
.finally(() => {
|
|
994
|
+
running.delete(st.id);
|
|
995
|
+
});
|
|
996
|
+
running.set(st.id, promise);
|
|
997
|
+
started = true;
|
|
998
|
+
}
|
|
999
|
+
if (running.size === 0) {
|
|
1000
|
+
if (!started)
|
|
1001
|
+
break; // nothing running, nothing ready = dependency cycle
|
|
1002
|
+
continue;
|
|
1003
|
+
}
|
|
1004
|
+
// Wait for at least one to finish
|
|
1005
|
+
await Promise.race(running.values());
|
|
1006
|
+
}
|
|
1007
|
+
// Drain any stragglers (only reachable if the loop exited via a cycle break)
|
|
1008
|
+
if (running.size > 0) {
|
|
1009
|
+
await Promise.allSettled(running.values());
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
_make_worker(worker_id) {
|
|
1013
|
+
// Determine whether the currently loaded model supports tool calling
|
|
1014
|
+
// by checking against the explicit whitelist in config.
|
|
1015
|
+
const cfg = this.agent.config;
|
|
1016
|
+
const capableRaw = cfg.swarm_tool_capable_models;
|
|
1017
|
+
const capable_models = Array.isArray(capableRaw) ? capableRaw.map((c) => String(c)) : [];
|
|
1018
|
+
const current_model = currentModelPath(cfg);
|
|
1019
|
+
// Match by full path or just filename
|
|
1020
|
+
const current_basename = path.basename(current_model);
|
|
1021
|
+
const tool_capable = capable_models.length === 0 || // if list is empty, assume all models can use tools
|
|
1022
|
+
capable_models.includes(current_model) ||
|
|
1023
|
+
capable_models.some((c) => path.basename(c) === current_basename);
|
|
1024
|
+
if (!tool_capable) {
|
|
1025
|
+
info(`[Swarm] Worker ${worker_id} → text-only mode (${current_basename})`);
|
|
1026
|
+
}
|
|
1027
|
+
return new SwarmWorker({
|
|
1028
|
+
worker_id,
|
|
1029
|
+
tool_map: this._toolMap(),
|
|
1030
|
+
llm: this._llm(),
|
|
1031
|
+
workspace: this._workspace(),
|
|
1032
|
+
max_steps: 8,
|
|
1033
|
+
on_progress: this.on_progress,
|
|
1034
|
+
tool_capable,
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
/** Build context string from already-completed sub-tasks. */
|
|
1038
|
+
_build_context(completed, exclude_id = null) {
|
|
1039
|
+
const parts = [];
|
|
1040
|
+
for (const tid of [...completed.keys()].sort((x, y) => x - y)) {
|
|
1041
|
+
if (tid === exclude_id) {
|
|
1042
|
+
continue;
|
|
1043
|
+
}
|
|
1044
|
+
const st = completed.get(tid);
|
|
1045
|
+
if (!st)
|
|
1046
|
+
continue;
|
|
1047
|
+
if (st.status === "done" && st.result) {
|
|
1048
|
+
parts.push(`Task ${tid} (${st.description.slice(0, 60)}): ${st.result.slice(0, 300)}`);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
return parts.slice(0, 3).join("\n"); // limit context size
|
|
1052
|
+
}
|
|
1053
|
+
_emit(event, data) {
|
|
1054
|
+
if (this.on_progress) {
|
|
1055
|
+
try {
|
|
1056
|
+
this.on_progress({ event, ...data });
|
|
1057
|
+
}
|
|
1058
|
+
catch {
|
|
1059
|
+
/* progress callbacks must never break the swarm */
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
//# sourceMappingURL=swarm.js.map
|