@swarmclawai/swarmclaw 0.8.4 → 0.8.7
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 +9 -9
- package/bin/swarmclaw.js +5 -1
- package/bin/worker-cmd.js +73 -0
- package/package.json +2 -1
- package/src/app/api/agents/[id]/route.ts +17 -7
- package/src/app/api/agents/route.ts +21 -8
- package/src/app/api/approvals/route.test.ts +6 -6
- package/src/app/api/approvals/route.ts +2 -1
- package/src/app/api/auth/route.ts +2 -3
- package/src/app/api/chatrooms/[id]/chat/route.test.ts +299 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +3 -2
- package/src/app/api/chatrooms/[id]/route.ts +7 -6
- package/src/app/api/chats/[id]/chat/route.test.ts +496 -0
- package/src/app/api/chats/[id]/chat/route.ts +7 -3
- package/src/app/api/chats/[id]/clear/route.ts +9 -9
- package/src/app/api/chats/[id]/devserver/route.ts +2 -1
- package/src/app/api/chats/[id]/edit-resend/route.ts +3 -4
- package/src/app/api/chats/[id]/fork/route.ts +3 -5
- package/src/app/api/chats/[id]/restore/route.ts +6 -7
- package/src/app/api/chats/[id]/retry/route.ts +3 -4
- package/src/app/api/chats/[id]/route.ts +61 -62
- package/src/app/api/chats/route.ts +7 -1
- package/src/app/api/connectors/[id]/route.ts +7 -8
- package/src/app/api/connectors/route.ts +5 -4
- package/src/app/api/eval/run/route.ts +2 -1
- package/src/app/api/eval/suite/route.ts +2 -1
- package/src/app/api/external-agents/route.test.ts +1 -1
- package/src/app/api/external-agents/route.ts +2 -2
- package/src/app/api/files/serve/route.ts +1 -1
- package/src/app/api/gateways/[id]/route.ts +7 -5
- package/src/app/api/gateways/route.ts +1 -1
- package/src/app/api/knowledge/upload/route.ts +1 -1
- package/src/app/api/logs/route.ts +5 -7
- package/src/app/api/memory-images/[filename]/route.ts +2 -3
- package/src/app/api/openclaw/agent-files/route.ts +4 -3
- package/src/app/api/openclaw/approvals/route.ts +3 -4
- package/src/app/api/openclaw/config-sync/route.ts +3 -2
- package/src/app/api/openclaw/cron/route.ts +3 -2
- package/src/app/api/openclaw/dotenv-keys/route.ts +2 -1
- package/src/app/api/openclaw/exec-config/route.ts +3 -2
- package/src/app/api/openclaw/gateway/route.ts +5 -4
- package/src/app/api/openclaw/history/route.ts +3 -2
- package/src/app/api/openclaw/media/route.ts +2 -1
- package/src/app/api/openclaw/permissions/route.ts +3 -2
- package/src/app/api/openclaw/sandbox-env/route.ts +3 -2
- package/src/app/api/openclaw/skills/install/route.ts +2 -1
- package/src/app/api/openclaw/skills/remove/route.ts +2 -1
- package/src/app/api/openclaw/skills/route.ts +3 -2
- package/src/app/api/orchestrator/run/route.ts +5 -14
- package/src/app/api/perf/route.ts +43 -0
- package/src/app/api/plugins/dependencies/route.ts +2 -1
- package/src/app/api/plugins/install/route.ts +2 -1
- package/src/app/api/plugins/marketplace/route.ts +3 -2
- package/src/app/api/plugins/settings/route.ts +2 -1
- package/src/app/api/preview-server/route.ts +11 -10
- package/src/app/api/projects/[id]/route.ts +1 -1
- package/src/app/api/schedules/[id]/route.test.ts +128 -0
- package/src/app/api/schedules/[id]/route.ts +43 -43
- package/src/app/api/schedules/[id]/run/route.ts +11 -62
- package/src/app/api/schedules/route.ts +21 -87
- package/src/app/api/settings/route.ts +2 -0
- package/src/app/api/setup/doctor/route.ts +9 -8
- package/src/app/api/tasks/[id]/approve/route.ts +33 -30
- package/src/app/api/tasks/[id]/route.ts +12 -35
- package/src/app/api/tasks/import/github/route.ts +2 -1
- package/src/app/api/tasks/route.ts +79 -91
- package/src/app/api/wallets/[id]/approve/route.ts +2 -1
- package/src/app/api/wallets/[id]/route.ts +13 -19
- package/src/app/api/wallets/[id]/send/route.ts +2 -1
- package/src/app/api/wallets/route.ts +2 -1
- package/src/app/api/webhooks/[id]/route.ts +2 -1
- package/src/app/api/webhooks/route.test.ts +3 -1
- package/src/app/page.tsx +23 -331
- package/src/cli/index.js +19 -0
- package/src/cli/index.ts +38 -7
- package/src/cli/spec.js +9 -0
- package/src/components/activity/activity-feed.tsx +7 -4
- package/src/components/agents/agent-card.tsx +32 -6
- package/src/components/agents/agent-chat-list.tsx +55 -22
- package/src/components/agents/agent-files-editor.tsx +3 -2
- package/src/components/agents/agent-sheet.tsx +123 -22
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/agents/openclaw-skills-panel.tsx +2 -1
- package/src/components/agents/trash-list.tsx +1 -1
- package/src/components/auth/access-key-gate.tsx +8 -2
- package/src/components/auth/setup-wizard.tsx +10 -9
- package/src/components/auth/user-picker.tsx +3 -2
- package/src/components/chat/chat-area.tsx +20 -1
- package/src/components/chat/chat-card.tsx +18 -3
- package/src/components/chat/chat-header.tsx +24 -4
- package/src/components/chat/chat-list.tsx +2 -11
- package/src/components/chat/heartbeat-history-panel.tsx +2 -1
- package/src/components/chat/message-bubble.tsx +45 -6
- package/src/components/chat/message-list.tsx +280 -145
- package/src/components/chat/streaming-bubble.tsx +217 -60
- package/src/components/chat/swarm-panel.test.ts +274 -0
- package/src/components/chat/swarm-panel.tsx +410 -0
- package/src/components/chat/swarm-status-card.tsx +346 -0
- package/src/components/chat/tool-call-bubble.tsx +48 -23
- package/src/components/chatrooms/chatroom-list.tsx +8 -5
- package/src/components/chatrooms/chatroom-message.tsx +10 -7
- package/src/components/chatrooms/chatroom-view.tsx +12 -9
- package/src/components/connectors/connector-health.tsx +6 -4
- package/src/components/connectors/connector-list.tsx +16 -11
- package/src/components/connectors/connector-sheet.tsx +12 -6
- package/src/components/home/home-view.tsx +38 -24
- package/src/components/input/chat-input.tsx +10 -1
- package/src/components/layout/app-layout.tsx +2 -38
- package/src/components/layout/sheet-layer.tsx +50 -0
- package/src/components/mcp-servers/mcp-server-list.tsx +37 -5
- package/src/components/mcp-servers/mcp-server-sheet.tsx +12 -2
- package/src/components/plugins/plugin-list.tsx +8 -4
- package/src/components/plugins/plugin-sheet.tsx +2 -1
- package/src/components/providers/provider-list.tsx +3 -2
- package/src/components/providers/provider-sheet.tsx +2 -1
- package/src/components/runs/run-list.tsx +11 -7
- package/src/components/schedules/schedule-card.tsx +5 -3
- package/src/components/shared/agent-switch-dialog.tsx +1 -1
- package/src/components/shared/attachment-chip.tsx +19 -3
- package/src/components/shared/notification-center.tsx +6 -3
- package/src/components/shared/settings/plugin-manager.tsx +3 -2
- package/src/components/shared/settings/section-embedding.tsx +2 -1
- package/src/components/shared/settings/section-orchestrator.tsx +2 -1
- package/src/components/shared/settings/section-user-preferences.tsx +107 -0
- package/src/components/shared/settings/settings-page.tsx +13 -9
- package/src/components/skills/clawhub-browser.tsx +15 -4
- package/src/components/skills/skill-list.tsx +15 -4
- package/src/components/tasks/approvals-panel.tsx +2 -1
- package/src/components/tasks/task-board.tsx +35 -37
- package/src/components/tasks/task-sheet.tsx +4 -3
- package/src/components/ui/full-screen-loader.tsx +164 -0
- package/src/components/wallets/wallet-approval-dialog.tsx +2 -1
- package/src/components/wallets/wallet-panel.tsx +6 -5
- package/src/components/wallets/wallet-section.tsx +3 -2
- package/src/components/webhooks/webhook-list.tsx +4 -5
- package/src/components/webhooks/webhook-sheet.tsx +6 -6
- package/src/hooks/use-app-bootstrap.ts +202 -0
- package/src/hooks/use-mounted-ref.ts +14 -0
- package/src/hooks/use-now.ts +31 -0
- package/src/hooks/use-openclaw-gateway.ts +2 -1
- package/src/instrumentation.ts +20 -8
- package/src/lib/agent-default-tools.test.ts +52 -0
- package/src/lib/agent-default-tools.ts +40 -0
- package/src/lib/api-client.test.ts +21 -0
- package/src/lib/api-client.ts +6 -11
- package/src/lib/canvas-content.test.ts +360 -0
- package/src/lib/chat-streaming-state.test.ts +49 -2
- package/src/lib/chat-streaming-state.ts +26 -10
- package/src/lib/fetch-timeout.test.ts +54 -0
- package/src/lib/fetch-timeout.ts +60 -3
- package/src/lib/live-tool-events.test.ts +77 -0
- package/src/lib/live-tool-events.ts +73 -0
- package/src/lib/local-observability.test.ts +2 -2
- package/src/lib/openclaw-endpoint.test.ts +1 -1
- package/src/lib/providers/anthropic.ts +12 -16
- package/src/lib/providers/index.ts +4 -2
- package/src/lib/providers/ollama.ts +9 -6
- package/src/lib/providers/openai.ts +11 -14
- package/src/lib/runtime-env.test.ts +8 -8
- package/src/lib/schedule-dedupe-advanced.test.ts +2 -2
- package/src/lib/schedule-dedupe.test.ts +1 -1
- package/src/lib/schedule-dedupe.ts +3 -2
- package/src/lib/server/agent-thread-session.test.ts +6 -6
- package/src/lib/server/agent-thread-session.ts +6 -9
- package/src/lib/server/alert-dispatch.ts +2 -1
- package/src/lib/server/api-routes.test.ts +6 -6
- package/src/lib/server/approval-connector-notify.test.ts +4 -4
- package/src/lib/server/approvals-auto-approve.test.ts +29 -29
- package/src/lib/server/approvals.test.ts +317 -0
- package/src/lib/server/approvals.ts +5 -4
- package/src/lib/server/autonomy-runtime.test.ts +11 -11
- package/src/lib/server/browser-state.ts +2 -2
- package/src/lib/server/capability-router.test.ts +1 -1
- package/src/lib/server/capability-router.ts +3 -2
- package/src/lib/server/chat-execution-advanced.test.ts +15 -2
- package/src/lib/server/chat-execution-connector-delivery.ts +67 -0
- package/src/lib/server/chat-execution-disabled.test.ts +3 -3
- package/src/lib/server/chat-execution-eval-history.test.ts +3 -3
- package/src/lib/server/chat-execution-heartbeat.test.ts +42 -1
- package/src/lib/server/chat-execution-session-sync.test.ts +119 -0
- package/src/lib/server/chat-execution-tool-events.ts +116 -0
- package/src/lib/server/chat-execution-utils.test.ts +479 -0
- package/src/lib/server/chat-execution-utils.ts +533 -0
- package/src/lib/server/chat-execution.ts +153 -748
- package/src/lib/server/chat-streaming-utils.ts +174 -0
- package/src/lib/server/chat-turn-tool-routing.ts +310 -0
- package/src/lib/server/chatroom-session-persistence.test.ts +2 -2
- package/src/lib/server/clawhub-client.ts +2 -1
- package/src/lib/server/collection-helpers.test.ts +92 -0
- package/src/lib/server/collection-helpers.ts +25 -3
- package/src/lib/server/connectors/access.ts +146 -0
- package/src/lib/server/connectors/bluebubbles.test.ts +1 -1
- package/src/lib/server/connectors/bluebubbles.ts +4 -4
- package/src/lib/server/connectors/commands.ts +367 -0
- package/src/lib/server/connectors/connector-routing.test.ts +4 -4
- package/src/lib/server/connectors/delivery.ts +142 -0
- package/src/lib/server/connectors/discord.ts +37 -40
- package/src/lib/server/connectors/email.ts +11 -10
- package/src/lib/server/connectors/googlechat.ts +4 -4
- package/src/lib/server/connectors/inbound-audio-transcription.ts +2 -1
- package/src/lib/server/connectors/ingress-delivery.ts +23 -0
- package/src/lib/server/connectors/manager-roundtrip.test.ts +300 -0
- package/src/lib/server/connectors/manager.test.ts +352 -77
- package/src/lib/server/connectors/manager.ts +134 -673
- package/src/lib/server/connectors/matrix.ts +4 -4
- package/src/lib/server/connectors/message-sentinel.ts +7 -0
- package/src/lib/server/connectors/openclaw.test.ts +1 -1
- package/src/lib/server/connectors/openclaw.ts +8 -10
- package/src/lib/server/connectors/outbox.test.ts +192 -0
- package/src/lib/server/connectors/outbox.ts +369 -0
- package/src/lib/server/connectors/pairing.test.ts +18 -1
- package/src/lib/server/connectors/pairing.ts +49 -4
- package/src/lib/server/connectors/policy.ts +9 -3
- package/src/lib/server/connectors/reconnect-state.ts +71 -0
- package/src/lib/server/connectors/response-media.ts +256 -0
- package/src/lib/server/connectors/runtime-state.ts +67 -0
- package/src/lib/server/connectors/session.test.ts +357 -0
- package/src/lib/server/connectors/session.ts +422 -0
- package/src/lib/server/connectors/signal.ts +7 -7
- package/src/lib/server/connectors/slack.ts +43 -43
- package/src/lib/server/connectors/teams.ts +4 -4
- package/src/lib/server/connectors/telegram.ts +37 -43
- package/src/lib/server/connectors/types.ts +31 -1
- package/src/lib/server/connectors/whatsapp.test.ts +108 -0
- package/src/lib/server/connectors/whatsapp.ts +106 -34
- package/src/lib/server/context-manager.test.ts +409 -0
- package/src/lib/server/cost.test.ts +1 -1
- package/src/lib/server/daemon-policy.ts +78 -0
- package/src/lib/server/daemon-state-connectors.test.ts +167 -0
- package/src/lib/server/daemon-state.test.ts +283 -55
- package/src/lib/server/daemon-state.ts +106 -109
- package/src/lib/server/data-dir.test.ts +5 -5
- package/src/lib/server/data-dir.ts +4 -0
- package/src/lib/server/delegation-jobs-advanced.test.ts +1 -1
- package/src/lib/server/delegation-jobs.test.ts +87 -0
- package/src/lib/server/delegation-jobs.ts +42 -48
- package/src/lib/server/devserver-launch.ts +1 -1
- package/src/lib/server/document-utils.ts +7 -9
- package/src/lib/server/elevenlabs.ts +2 -1
- package/src/lib/server/embeddings.test.ts +105 -0
- package/src/lib/server/ethereum.ts +3 -2
- package/src/lib/server/eval/agent-regression.ts +3 -2
- package/src/lib/server/eval/runner.ts +2 -1
- package/src/lib/server/eval/scorer.ts +2 -1
- package/src/lib/server/evm-swap.ts +2 -1
- package/src/lib/server/gateway/protocol.test.ts +1 -1
- package/src/lib/server/guardian.ts +2 -1
- package/src/lib/server/heartbeat-blocked-suppression.test.ts +151 -0
- package/src/lib/server/heartbeat-service-timer.test.ts +6 -6
- package/src/lib/server/heartbeat-service.test.ts +406 -0
- package/src/lib/server/heartbeat-service.ts +54 -7
- package/src/lib/server/heartbeat-wake.test.ts +19 -0
- package/src/lib/server/heartbeat-wake.ts +17 -16
- package/src/lib/server/integrity-monitor.test.ts +149 -0
- package/src/lib/server/json-utils.ts +22 -0
- package/src/lib/server/knowledge-db.test.ts +13 -13
- package/src/lib/server/link-understanding.ts +2 -1
- package/src/lib/server/llm-response-cache.test.ts +1 -1
- package/src/lib/server/main-agent-loop-advanced.test.ts +65 -3
- package/src/lib/server/main-agent-loop.test.ts +6 -6
- package/src/lib/server/main-agent-loop.ts +21 -7
- package/src/lib/server/mcp-client.test.ts +1 -1
- package/src/lib/server/mcp-conformance.test.ts +1 -1
- package/src/lib/server/mcp-conformance.ts +3 -2
- package/src/lib/server/memory-consolidation.ts +2 -1
- package/src/lib/server/memory-db.test.ts +485 -0
- package/src/lib/server/memory-db.ts +39 -26
- package/src/lib/server/memory-graph.test.ts +2 -2
- package/src/lib/server/memory-policy.test.ts +7 -7
- package/src/lib/server/memory-retrieval.test.ts +1 -1
- package/src/lib/server/openclaw-config-sync.ts +2 -1
- package/src/lib/server/openclaw-deploy.test.ts +1 -1
- package/src/lib/server/openclaw-deploy.ts +8 -12
- package/src/lib/server/openclaw-exec-config.ts +2 -1
- package/src/lib/server/openclaw-gateway.ts +6 -7
- package/src/lib/server/openclaw-skills-normalize.ts +2 -1
- package/src/lib/server/openclaw-sync.ts +7 -5
- package/src/lib/server/orchestrator-lg-structure.test.ts +17 -0
- package/src/lib/server/orchestrator-lg.ts +199 -327
- package/src/lib/server/path-utils.ts +31 -0
- package/src/lib/server/perf.ts +161 -0
- package/src/lib/server/plugins-approval-guidance.ts +115 -0
- package/src/lib/server/plugins.test.ts +1 -1
- package/src/lib/server/plugins.ts +22 -132
- package/src/lib/server/process-manager.ts +5 -8
- package/src/lib/server/provider-health.test.ts +137 -0
- package/src/lib/server/provider-health.ts +3 -3
- package/src/lib/server/provider-model-discovery.ts +3 -12
- package/src/lib/server/queue-followups.test.ts +9 -9
- package/src/lib/server/queue-reconcile.test.ts +2 -2
- package/src/lib/server/queue-recovery.test.ts +269 -0
- package/src/lib/server/queue.test.ts +570 -0
- package/src/lib/server/queue.ts +62 -455
- package/src/lib/server/resolve-image.ts +30 -0
- package/src/lib/server/runtime-settings.test.ts +4 -4
- package/src/lib/server/runtime-storage-write-paths.test.ts +60 -0
- package/src/lib/server/schedule-normalization.test.ts +279 -0
- package/src/lib/server/schedule-service.ts +263 -0
- package/src/lib/server/scheduler.ts +17 -74
- package/src/lib/server/session-mailbox.test.ts +191 -0
- package/src/lib/server/session-run-manager.test.ts +640 -0
- package/src/lib/server/session-run-manager.ts +59 -15
- package/src/lib/server/session-tools/autonomy-tools.test.ts +20 -20
- package/src/lib/server/session-tools/calendar.ts +2 -1
- package/src/lib/server/session-tools/canvas.ts +2 -1
- package/src/lib/server/session-tools/chatroom.ts +2 -1
- package/src/lib/server/session-tools/connector.ts +26 -28
- package/src/lib/server/session-tools/context-mgmt.ts +3 -2
- package/src/lib/server/session-tools/crawl.ts +4 -3
- package/src/lib/server/session-tools/crud.ts +105 -324
- package/src/lib/server/session-tools/delegate-fallback.test.ts +9 -9
- package/src/lib/server/session-tools/delegate.ts +6 -8
- package/src/lib/server/session-tools/discovery-approvals.test.ts +15 -15
- package/src/lib/server/session-tools/discovery.ts +4 -3
- package/src/lib/server/session-tools/document.ts +2 -1
- package/src/lib/server/session-tools/email.ts +2 -1
- package/src/lib/server/session-tools/extract.ts +2 -1
- package/src/lib/server/session-tools/file.ts +4 -3
- package/src/lib/server/session-tools/http.ts +2 -1
- package/src/lib/server/session-tools/human-loop.ts +2 -1
- package/src/lib/server/session-tools/image-gen.ts +4 -3
- package/src/lib/server/session-tools/index.ts +26 -30
- package/src/lib/server/session-tools/mailbox.ts +2 -1
- package/src/lib/server/session-tools/manage-connectors.test.ts +4 -4
- package/src/lib/server/session-tools/manage-schedules.test.ts +12 -12
- package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +5 -5
- package/src/lib/server/session-tools/manage-tasks.test.ts +2 -2
- package/src/lib/server/session-tools/monitor.ts +2 -1
- package/src/lib/server/session-tools/platform.ts +2 -1
- package/src/lib/server/session-tools/plugin-creator.ts +2 -1
- package/src/lib/server/session-tools/replicate.ts +3 -2
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +6 -6
- package/src/lib/server/session-tools/shell.ts +4 -9
- package/src/lib/server/session-tools/subagent.ts +322 -170
- package/src/lib/server/session-tools/table.ts +6 -5
- package/src/lib/server/session-tools/wallet-tool.test.ts +3 -3
- package/src/lib/server/session-tools/wallet.ts +7 -6
- package/src/lib/server/session-tools/web-browser-config.test.ts +1 -0
- package/src/lib/server/session-tools/web-utils.ts +317 -0
- package/src/lib/server/session-tools/web.ts +62 -328
- package/src/lib/server/skill-prompt-budget.test.ts +1 -1
- package/src/lib/server/skills-normalize.ts +2 -1
- package/src/lib/server/storage-item-access.test.ts +302 -0
- package/src/lib/server/storage.ts +366 -314
- package/src/lib/server/stream-agent-chat.test.ts +82 -3
- package/src/lib/server/stream-agent-chat.ts +146 -510
- package/src/lib/server/stream-continuation.ts +412 -0
- package/src/lib/server/subagent-lineage.test.ts +647 -0
- package/src/lib/server/subagent-lineage.ts +435 -0
- package/src/lib/server/subagent-runtime.test.ts +484 -0
- package/src/lib/server/subagent-runtime.ts +419 -0
- package/src/lib/server/subagent-swarm.test.ts +391 -0
- package/src/lib/server/subagent-swarm.ts +564 -0
- package/src/lib/server/system-events.ts +3 -3
- package/src/lib/server/task-followups.test.ts +491 -0
- package/src/lib/server/task-followups.ts +391 -0
- package/src/lib/server/task-lifecycle.test.ts +205 -0
- package/src/lib/server/task-lifecycle.ts +200 -0
- package/src/lib/server/task-quality-gate.test.ts +1 -1
- package/src/lib/server/task-resume.ts +208 -0
- package/src/lib/server/task-service.test.ts +108 -0
- package/src/lib/server/task-service.ts +264 -0
- package/src/lib/server/task-validation.test.ts +1 -1
- package/src/lib/server/test-utils/run-with-temp-data-dir.ts +42 -0
- package/src/lib/server/tool-capability-policy.test.ts +2 -2
- package/src/lib/server/tool-capability-policy.ts +3 -2
- package/src/lib/server/tool-planning.ts +2 -1
- package/src/lib/server/tool-retry.ts +2 -3
- package/src/lib/server/wake-dispatcher.test.ts +303 -0
- package/src/lib/server/wake-dispatcher.ts +318 -0
- package/src/lib/server/wake-mode.test.ts +161 -0
- package/src/lib/server/wake-mode.ts +174 -0
- package/src/lib/server/wallet-service.ts +8 -9
- package/src/lib/server/watch-jobs.ts +2 -1
- package/src/lib/server/workspace-context.ts +2 -2
- package/src/lib/shared-utils.test.ts +142 -0
- package/src/lib/shared-utils.ts +62 -0
- package/src/lib/tool-event-summary.ts +2 -1
- package/src/lib/view-routes.test.ts +100 -0
- package/src/lib/wallet.test.ts +322 -6
- package/src/proxy.test.ts +4 -4
- package/src/proxy.ts +2 -3
- package/src/stores/set-if-changed.ts +40 -0
- package/src/stores/slices/agent-slice.ts +111 -0
- package/src/stores/slices/auth-slice.ts +25 -0
- package/src/stores/slices/data-slice.ts +301 -0
- package/src/stores/slices/index.ts +7 -0
- package/src/stores/slices/session-slice.ts +112 -0
- package/src/stores/slices/task-slice.ts +63 -0
- package/src/stores/slices/ui-slice.ts +192 -0
- package/src/stores/use-app-store.ts +17 -822
- package/src/stores/use-approval-store.ts +2 -1
- package/src/stores/use-chat-store.ts +8 -1
- package/src/types/index.ts +10 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagent Swarm — Parallel Spawn & Result Aggregation
|
|
3
|
+
*
|
|
4
|
+
* Unified module for spawning multiple subagents in parallel and collecting
|
|
5
|
+
* their results. Supports both event-driven (swarm) and poll-based (aggregate)
|
|
6
|
+
* result collection patterns.
|
|
7
|
+
*
|
|
8
|
+
* Replaces the separate subagent-batch module — batch operations are now
|
|
9
|
+
* thin wrappers over spawnSwarm.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { genId } from '@/lib/id'
|
|
13
|
+
import { errorMessage, hmrSingleton, sleep } from '@/lib/shared-utils'
|
|
14
|
+
import { notify } from './ws-hub'
|
|
15
|
+
import {
|
|
16
|
+
spawnSubagent,
|
|
17
|
+
type SubagentContext,
|
|
18
|
+
type SubagentHandle,
|
|
19
|
+
type SubagentResult,
|
|
20
|
+
} from './subagent-runtime'
|
|
21
|
+
import { loadSessions } from './storage'
|
|
22
|
+
import { getDelegationJob } from './delegation-jobs'
|
|
23
|
+
import {
|
|
24
|
+
getLineageNode,
|
|
25
|
+
cancelLineageNode,
|
|
26
|
+
type SubagentState,
|
|
27
|
+
} from './subagent-lineage'
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Types
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
export type SwarmStatus = 'spawning' | 'running' | 'completed' | 'partial' | 'failed'
|
|
34
|
+
|
|
35
|
+
export interface SwarmMember {
|
|
36
|
+
/** Position in the spawn order */
|
|
37
|
+
index: number
|
|
38
|
+
/** Agent handle from spawnSubagent */
|
|
39
|
+
handle: SubagentHandle
|
|
40
|
+
/** Result (populated when the agent completes) */
|
|
41
|
+
result: SubagentResult | null
|
|
42
|
+
/** Error if spawn itself failed (before execution) */
|
|
43
|
+
spawnError: string | null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface SwarmHandle {
|
|
47
|
+
/** Unique swarm ID */
|
|
48
|
+
swarmId: string
|
|
49
|
+
/** Session that spawned this swarm */
|
|
50
|
+
parentSessionId: string | null
|
|
51
|
+
/** All members (spawned or failed-to-spawn) */
|
|
52
|
+
members: SwarmMember[]
|
|
53
|
+
/** Swarm-level status */
|
|
54
|
+
status: SwarmStatus
|
|
55
|
+
/** When the swarm was created */
|
|
56
|
+
createdAt: number
|
|
57
|
+
/** When all members finished (null if still running) */
|
|
58
|
+
completedAt: number | null
|
|
59
|
+
/** Promise that resolves when ALL members complete */
|
|
60
|
+
allSettled: Promise<SwarmAggregateResult>
|
|
61
|
+
/** Promise that resolves when the FIRST member completes */
|
|
62
|
+
firstSettled: Promise<{ index: number; result: SubagentResult }>
|
|
63
|
+
/** Cancel all running members */
|
|
64
|
+
cancelAll: () => void
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface SwarmAggregateResult {
|
|
68
|
+
swarmId: string
|
|
69
|
+
parentSessionId: string | null
|
|
70
|
+
totalSpawned: number
|
|
71
|
+
totalCompleted: number
|
|
72
|
+
totalFailed: number
|
|
73
|
+
totalCancelled: number
|
|
74
|
+
totalSpawnErrors: number
|
|
75
|
+
durationMs: number
|
|
76
|
+
results: Array<{
|
|
77
|
+
index: number
|
|
78
|
+
agentId: string
|
|
79
|
+
agentName: string
|
|
80
|
+
jobId: string
|
|
81
|
+
sessionId: string
|
|
82
|
+
status: SubagentResult['status'] | 'spawn_error'
|
|
83
|
+
response: string | null
|
|
84
|
+
error: string | null
|
|
85
|
+
durationMs: number
|
|
86
|
+
}>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface BatchSpawnInput {
|
|
90
|
+
/** Tasks to spawn — each gets its own subagent */
|
|
91
|
+
tasks: Array<{
|
|
92
|
+
agentId: string
|
|
93
|
+
message: string
|
|
94
|
+
cwd?: string
|
|
95
|
+
shareBrowserProfile?: boolean
|
|
96
|
+
}>
|
|
97
|
+
/** Optional swarm-level label */
|
|
98
|
+
label?: string
|
|
99
|
+
/** Callback when each member completes */
|
|
100
|
+
onMemberComplete?: (member: SwarmMember, swarm: SwarmHandle) => void
|
|
101
|
+
/** Callback when all members complete */
|
|
102
|
+
onSwarmComplete?: (result: SwarmAggregateResult) => void
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// Batch types (absorbed from subagent-batch)
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
export interface BatchTask {
|
|
110
|
+
agentId: string
|
|
111
|
+
message: string
|
|
112
|
+
cwd?: string
|
|
113
|
+
shareBrowserProfile?: boolean
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface AggregatedResult {
|
|
117
|
+
results: Array<{
|
|
118
|
+
jobId: string
|
|
119
|
+
status: string
|
|
120
|
+
response: string | null
|
|
121
|
+
error: string | null
|
|
122
|
+
agentName: string | null
|
|
123
|
+
}>
|
|
124
|
+
pending: string[]
|
|
125
|
+
allCompleted: boolean
|
|
126
|
+
completed: number
|
|
127
|
+
failed: number
|
|
128
|
+
cancelled: number
|
|
129
|
+
total: number
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// Storage (globalThis-scoped for HMR safety)
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
const swarmRegistry = hmrSingleton('__swarmclaw_swarm_registry__', () => new Map<string, SwarmHandle>())
|
|
137
|
+
|
|
138
|
+
function notifySwarmChanged() {
|
|
139
|
+
notify('swarm_status')
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Core: Spawn Swarm
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Spawn multiple subagents in parallel. Returns immediately with handles
|
|
148
|
+
* for all agents. Each agent runs with waitForCompletion: false.
|
|
149
|
+
*
|
|
150
|
+
* Usage:
|
|
151
|
+
* const swarm = spawnSwarm({
|
|
152
|
+
* tasks: [
|
|
153
|
+
* { agentId: 'researcher', message: 'Find API docs' },
|
|
154
|
+
* { agentId: 'coder', message: 'Scaffold the module' },
|
|
155
|
+
* ],
|
|
156
|
+
* }, { sessionId: parentSession, cwd: '/workspace' })
|
|
157
|
+
*
|
|
158
|
+
* const aggregate = await swarm.allSettled
|
|
159
|
+
*/
|
|
160
|
+
export function spawnSwarm(
|
|
161
|
+
input: BatchSpawnInput,
|
|
162
|
+
context: SubagentContext,
|
|
163
|
+
): SwarmHandle {
|
|
164
|
+
const swarmId = genId(10)
|
|
165
|
+
const createdAt = Date.now()
|
|
166
|
+
const members: SwarmMember[] = []
|
|
167
|
+
|
|
168
|
+
// Pre-load sessions once for all spawns (avoids N SQLite reads)
|
|
169
|
+
const cachedSessions = context._sessions ?? loadSessions()
|
|
170
|
+
const cachedContext: SubagentContext = { ...context, _sessions: cachedSessions }
|
|
171
|
+
|
|
172
|
+
// Spawn all agents — failures are captured per-member, not thrown
|
|
173
|
+
let spawnErrorCount = 0
|
|
174
|
+
for (let i = 0; i < input.tasks.length; i++) {
|
|
175
|
+
const task = input.tasks[i]
|
|
176
|
+
try {
|
|
177
|
+
const handle = spawnSubagent(
|
|
178
|
+
{
|
|
179
|
+
agentId: task.agentId,
|
|
180
|
+
message: task.message,
|
|
181
|
+
cwd: task.cwd,
|
|
182
|
+
shareBrowserProfile: task.shareBrowserProfile,
|
|
183
|
+
waitForCompletion: false,
|
|
184
|
+
},
|
|
185
|
+
cachedContext,
|
|
186
|
+
)
|
|
187
|
+
members.push({ index: i, handle, result: null, spawnError: null })
|
|
188
|
+
} catch (err: unknown) {
|
|
189
|
+
spawnErrorCount++
|
|
190
|
+
members.push({
|
|
191
|
+
index: i,
|
|
192
|
+
handle: null as unknown as SubagentHandle,
|
|
193
|
+
result: null,
|
|
194
|
+
spawnError: errorMessage(err),
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Incremental counters — O(1) per completion instead of O(n)
|
|
200
|
+
const counters = { completed: 0, failed: 0, cancelled: 0 }
|
|
201
|
+
|
|
202
|
+
// Track completion per member
|
|
203
|
+
const memberPromises: Promise<{ index: number; result: SubagentResult }>[] = []
|
|
204
|
+
|
|
205
|
+
const swarm: SwarmHandle = {
|
|
206
|
+
swarmId,
|
|
207
|
+
parentSessionId: context.sessionId || null,
|
|
208
|
+
members,
|
|
209
|
+
status: 'running',
|
|
210
|
+
createdAt,
|
|
211
|
+
completedAt: null,
|
|
212
|
+
allSettled: null as unknown as Promise<SwarmAggregateResult>,
|
|
213
|
+
firstSettled: null as unknown as Promise<{ index: number; result: SubagentResult }>,
|
|
214
|
+
cancelAll: () => {
|
|
215
|
+
for (const member of members) {
|
|
216
|
+
if (member.handle && !member.result && !member.spawnError) {
|
|
217
|
+
try {
|
|
218
|
+
member.handle.run.abort()
|
|
219
|
+
cancelLineageNode(member.handle.lineageId)
|
|
220
|
+
} catch { /* best-effort */ }
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
swarm.status = 'failed'
|
|
224
|
+
notifySwarmChanged()
|
|
225
|
+
},
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for (const member of members) {
|
|
229
|
+
if (member.spawnError || !member.handle) continue
|
|
230
|
+
|
|
231
|
+
const memberPromise = member.handle.promise.then((result) => {
|
|
232
|
+
member.result = result
|
|
233
|
+
|
|
234
|
+
// Increment counters
|
|
235
|
+
if (result.status === 'completed') counters.completed++
|
|
236
|
+
else if (result.status === 'failed' || result.status === 'timed_out') counters.failed++
|
|
237
|
+
else if (result.status === 'cancelled') counters.cancelled++
|
|
238
|
+
|
|
239
|
+
if (input.onMemberComplete) {
|
|
240
|
+
try { input.onMemberComplete(member, swarm) } catch { /* callback errors don't break the swarm */ }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Update swarm status using counters (O(1))
|
|
244
|
+
updateSwarmStatus(swarm, counters, spawnErrorCount)
|
|
245
|
+
notifySwarmChanged()
|
|
246
|
+
|
|
247
|
+
return { index: member.index, result }
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
memberPromises.push(memberPromise)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// allSettled — resolves when every member is done (or had a spawn error)
|
|
254
|
+
swarm.allSettled = Promise.allSettled(memberPromises).then(() => {
|
|
255
|
+
swarm.completedAt = Date.now()
|
|
256
|
+
updateSwarmStatus(swarm, counters, spawnErrorCount)
|
|
257
|
+
const aggregate = buildAggregateResult(swarm)
|
|
258
|
+
if (input.onSwarmComplete) {
|
|
259
|
+
try { input.onSwarmComplete(aggregate) } catch { /* callback errors don't break */ }
|
|
260
|
+
}
|
|
261
|
+
notifySwarmChanged()
|
|
262
|
+
return aggregate
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
// firstSettled — resolves when the first member completes
|
|
266
|
+
swarm.firstSettled = memberPromises.length > 0
|
|
267
|
+
? Promise.race(memberPromises)
|
|
268
|
+
: swarm.allSettled.then((agg) => {
|
|
269
|
+
// All members had spawn errors — return first spawn error entry
|
|
270
|
+
const first = agg.results[0]
|
|
271
|
+
return {
|
|
272
|
+
index: first?.index ?? -1,
|
|
273
|
+
result: {
|
|
274
|
+
jobId: first?.jobId ?? '',
|
|
275
|
+
sessionId: first?.sessionId ?? '',
|
|
276
|
+
lineageId: '',
|
|
277
|
+
agentId: first?.agentId ?? '',
|
|
278
|
+
agentName: first?.agentName ?? '',
|
|
279
|
+
status: 'failed' as const,
|
|
280
|
+
response: null,
|
|
281
|
+
error: first?.error ?? 'No members spawned',
|
|
282
|
+
depth: 0,
|
|
283
|
+
parentSessionId: context.sessionId || null,
|
|
284
|
+
childCount: 0,
|
|
285
|
+
durationMs: 0,
|
|
286
|
+
} satisfies SubagentResult,
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
// Register in swarm registry
|
|
291
|
+
swarmRegistry.set(swarmId, swarm)
|
|
292
|
+
notifySwarmChanged()
|
|
293
|
+
|
|
294
|
+
return swarm
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
// Status computation (O(1) with incremental counters)
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
|
|
301
|
+
function updateSwarmStatus(
|
|
302
|
+
swarm: SwarmHandle,
|
|
303
|
+
counters: { completed: number; failed: number; cancelled: number },
|
|
304
|
+
spawnErrors: number,
|
|
305
|
+
): void {
|
|
306
|
+
const total = swarm.members.length
|
|
307
|
+
const settled = spawnErrors + counters.completed + counters.failed + counters.cancelled
|
|
308
|
+
|
|
309
|
+
if (settled >= total) {
|
|
310
|
+
if (counters.failed + spawnErrors === total) {
|
|
311
|
+
swarm.status = 'failed'
|
|
312
|
+
} else if (counters.completed === total) {
|
|
313
|
+
swarm.status = 'completed'
|
|
314
|
+
} else {
|
|
315
|
+
swarm.status = 'partial'
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
swarm.status = 'running'
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
// Result aggregation
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
|
|
326
|
+
function buildAggregateResult(swarm: SwarmHandle): SwarmAggregateResult {
|
|
327
|
+
const results = swarm.members.map((member) => {
|
|
328
|
+
if (member.spawnError) {
|
|
329
|
+
return {
|
|
330
|
+
index: member.index,
|
|
331
|
+
agentId: '',
|
|
332
|
+
agentName: '',
|
|
333
|
+
jobId: '',
|
|
334
|
+
sessionId: '',
|
|
335
|
+
status: 'spawn_error' as const,
|
|
336
|
+
response: null,
|
|
337
|
+
error: member.spawnError,
|
|
338
|
+
durationMs: 0,
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const r = member.result
|
|
342
|
+
return {
|
|
343
|
+
index: member.index,
|
|
344
|
+
agentId: r?.agentId || member.handle?.agentId || '',
|
|
345
|
+
agentName: r?.agentName || member.handle?.agentName || '',
|
|
346
|
+
jobId: r?.jobId || member.handle?.jobId || '',
|
|
347
|
+
sessionId: r?.sessionId || member.handle?.sessionId || '',
|
|
348
|
+
status: r?.status || 'failed' as SubagentResult['status'],
|
|
349
|
+
response: r?.response || null,
|
|
350
|
+
error: r?.error || null,
|
|
351
|
+
durationMs: r?.durationMs || 0,
|
|
352
|
+
}
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
swarmId: swarm.swarmId,
|
|
357
|
+
parentSessionId: swarm.parentSessionId,
|
|
358
|
+
totalSpawned: swarm.members.length,
|
|
359
|
+
totalCompleted: results.filter((r) => r.status === 'completed').length,
|
|
360
|
+
totalFailed: results.filter((r) => r.status === 'failed' || r.status === 'timed_out').length,
|
|
361
|
+
totalCancelled: results.filter((r) => r.status === 'cancelled').length,
|
|
362
|
+
totalSpawnErrors: results.filter((r) => r.status === 'spawn_error').length,
|
|
363
|
+
durationMs: (swarm.completedAt || Date.now()) - swarm.createdAt,
|
|
364
|
+
results,
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ---------------------------------------------------------------------------
|
|
369
|
+
// Delegation job polling (absorbed from subagent-batch)
|
|
370
|
+
// ---------------------------------------------------------------------------
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Poll delegation jobs for instant snapshot of results.
|
|
374
|
+
* Useful when the caller only has job IDs (no SwarmHandle).
|
|
375
|
+
*/
|
|
376
|
+
export function aggregateResults(jobIds: string[]): AggregatedResult {
|
|
377
|
+
const results: AggregatedResult['results'] = []
|
|
378
|
+
const pending: string[] = []
|
|
379
|
+
let completed = 0
|
|
380
|
+
let failed = 0
|
|
381
|
+
let cancelled = 0
|
|
382
|
+
|
|
383
|
+
for (const jobId of jobIds) {
|
|
384
|
+
const job = getDelegationJob(jobId)
|
|
385
|
+
if (!job) {
|
|
386
|
+
results.push({
|
|
387
|
+
jobId,
|
|
388
|
+
status: 'not_found',
|
|
389
|
+
response: null,
|
|
390
|
+
error: `Job "${jobId}" not found`,
|
|
391
|
+
agentName: null,
|
|
392
|
+
})
|
|
393
|
+
failed++
|
|
394
|
+
continue
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
results.push({
|
|
398
|
+
jobId,
|
|
399
|
+
status: job.status,
|
|
400
|
+
response: job.resultPreview || job.result || null,
|
|
401
|
+
error: job.error || null,
|
|
402
|
+
agentName: job.agentName || null,
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
if (job.status === 'completed') completed++
|
|
406
|
+
else if (job.status === 'failed') failed++
|
|
407
|
+
else if (job.status === 'cancelled') cancelled++
|
|
408
|
+
else pending.push(jobId)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
results,
|
|
413
|
+
pending,
|
|
414
|
+
allCompleted: pending.length === 0,
|
|
415
|
+
completed,
|
|
416
|
+
failed,
|
|
417
|
+
cancelled,
|
|
418
|
+
total: jobIds.length,
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Wait for multiple jobs to complete (poll-based with timeout).
|
|
424
|
+
*/
|
|
425
|
+
export async function waitForAll(
|
|
426
|
+
jobIds: string[],
|
|
427
|
+
timeoutSec = 300,
|
|
428
|
+
): Promise<AggregatedResult> {
|
|
429
|
+
const timeoutAt = Date.now() + Math.max(1, timeoutSec) * 1000
|
|
430
|
+
const pollIntervalMs = 1000
|
|
431
|
+
|
|
432
|
+
while (Date.now() < timeoutAt) {
|
|
433
|
+
const snapshot = aggregateResults(jobIds)
|
|
434
|
+
if (snapshot.allCompleted) return snapshot
|
|
435
|
+
await sleep(pollIntervalMs)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Final snapshot after timeout
|
|
439
|
+
return aggregateResults(jobIds)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ---------------------------------------------------------------------------
|
|
443
|
+
// Query API (for UI / session tools)
|
|
444
|
+
// ---------------------------------------------------------------------------
|
|
445
|
+
|
|
446
|
+
export function getSwarm(swarmId: string): SwarmHandle | null {
|
|
447
|
+
return swarmRegistry.get(swarmId) || null
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export function listSwarms(parentSessionId?: string): SwarmHandle[] {
|
|
451
|
+
const all = Array.from(swarmRegistry.values())
|
|
452
|
+
if (parentSessionId) {
|
|
453
|
+
return all.filter((s) => s.parentSessionId === parentSessionId)
|
|
454
|
+
}
|
|
455
|
+
return all
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Get a serializable snapshot of a swarm's current state.
|
|
460
|
+
* Used by the UI to render SwarmStatusCard.
|
|
461
|
+
*/
|
|
462
|
+
export function getSwarmSnapshot(swarmId: string): SwarmSnapshot | null {
|
|
463
|
+
const swarm = swarmRegistry.get(swarmId)
|
|
464
|
+
if (!swarm) return null
|
|
465
|
+
return buildSwarmSnapshot(swarm)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
export interface SwarmMemberSnapshot {
|
|
469
|
+
index: number
|
|
470
|
+
agentId: string
|
|
471
|
+
agentName: string
|
|
472
|
+
jobId: string
|
|
473
|
+
sessionId: string
|
|
474
|
+
task: string
|
|
475
|
+
status: SubagentState | 'spawn_error'
|
|
476
|
+
resultPreview: string | null
|
|
477
|
+
error: string | null
|
|
478
|
+
durationMs: number
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export interface SwarmSnapshot {
|
|
482
|
+
swarmId: string
|
|
483
|
+
parentSessionId: string | null
|
|
484
|
+
status: SwarmStatus
|
|
485
|
+
createdAt: number
|
|
486
|
+
completedAt: number | null
|
|
487
|
+
memberCount: number
|
|
488
|
+
completedCount: number
|
|
489
|
+
failedCount: number
|
|
490
|
+
members: SwarmMemberSnapshot[]
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function buildSwarmSnapshot(swarm: SwarmHandle): SwarmSnapshot {
|
|
494
|
+
const members: SwarmMemberSnapshot[] = swarm.members.map((m) => {
|
|
495
|
+
if (m.spawnError || !m.handle) {
|
|
496
|
+
return {
|
|
497
|
+
index: m.index,
|
|
498
|
+
agentId: '',
|
|
499
|
+
agentName: '',
|
|
500
|
+
jobId: '',
|
|
501
|
+
sessionId: '',
|
|
502
|
+
task: '',
|
|
503
|
+
status: 'spawn_error' as const,
|
|
504
|
+
resultPreview: null,
|
|
505
|
+
error: m.spawnError || 'Spawn failed (no handle)',
|
|
506
|
+
durationMs: 0,
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
// Read state from lineage node (single source of truth) with fallbacks
|
|
510
|
+
const node = getLineageNode(m.handle.lineageId)
|
|
511
|
+
const status = m.result?.status ?? node?.status ?? 'running'
|
|
512
|
+
return {
|
|
513
|
+
index: m.index,
|
|
514
|
+
agentId: m.handle.agentId,
|
|
515
|
+
agentName: m.handle.agentName,
|
|
516
|
+
jobId: m.handle.jobId,
|
|
517
|
+
sessionId: m.handle.sessionId,
|
|
518
|
+
task: getDelegationJob(m.handle.jobId)?.task || '',
|
|
519
|
+
status,
|
|
520
|
+
resultPreview: m.result?.response?.slice(0, 500) || null,
|
|
521
|
+
error: m.result?.error || null,
|
|
522
|
+
durationMs: m.result?.durationMs || (Date.now() - swarm.createdAt),
|
|
523
|
+
}
|
|
524
|
+
})
|
|
525
|
+
|
|
526
|
+
return {
|
|
527
|
+
swarmId: swarm.swarmId,
|
|
528
|
+
parentSessionId: swarm.parentSessionId,
|
|
529
|
+
status: swarm.status,
|
|
530
|
+
createdAt: swarm.createdAt,
|
|
531
|
+
completedAt: swarm.completedAt,
|
|
532
|
+
memberCount: members.length,
|
|
533
|
+
completedCount: members.filter((m) => m.status === 'completed').length,
|
|
534
|
+
failedCount: members.filter((m) =>
|
|
535
|
+
m.status === 'failed' || m.status === 'timed_out' || m.status === 'spawn_error',
|
|
536
|
+
).length,
|
|
537
|
+
members,
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// ---------------------------------------------------------------------------
|
|
542
|
+
// Cleanup
|
|
543
|
+
// ---------------------------------------------------------------------------
|
|
544
|
+
|
|
545
|
+
export function removeSwarm(swarmId: string): boolean {
|
|
546
|
+
return swarmRegistry.delete(swarmId)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export function cleanupFinishedSwarms(maxAgeMs = 60 * 60_000): number {
|
|
550
|
+
const threshold = Date.now() - maxAgeMs
|
|
551
|
+
let cleaned = 0
|
|
552
|
+
for (const [id, swarm] of swarmRegistry.entries()) {
|
|
553
|
+
if (swarm.completedAt && swarm.completedAt < threshold) {
|
|
554
|
+
swarmRegistry.delete(id)
|
|
555
|
+
cleaned++
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return cleaned
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/** For tests only */
|
|
562
|
+
export function _clearSwarmRegistry(): void {
|
|
563
|
+
swarmRegistry.clear()
|
|
564
|
+
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Events are accumulated between heartbeat ticks and drained into heartbeat prompts.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { hmrSingleton } from '@/lib/shared-utils'
|
|
7
|
+
|
|
6
8
|
export interface SystemEvent {
|
|
7
9
|
text: string
|
|
8
10
|
timestamp: number
|
|
@@ -11,9 +13,7 @@ export interface SystemEvent {
|
|
|
11
13
|
|
|
12
14
|
const MAX_EVENTS_PER_SESSION = 20
|
|
13
15
|
|
|
14
|
-
const
|
|
15
|
-
const globalScope = globalThis as typeof globalThis & { [globalKey]?: Map<string, SystemEvent[]> }
|
|
16
|
-
const queues: Map<string, SystemEvent[]> = globalScope[globalKey] ?? (globalScope[globalKey] = new Map())
|
|
16
|
+
const queues: Map<string, SystemEvent[]> = hmrSingleton('__swarmclaw_system_events__', () => new Map<string, SystemEvent[]>())
|
|
17
17
|
|
|
18
18
|
/** Push an event for a session. Deduplicates consecutive identical text, caps at MAX_EVENTS_PER_SESSION. */
|
|
19
19
|
export function enqueueSystemEvent(sessionId: string, text: string, contextKey?: string): void {
|