@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,484 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import os from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { after, before, describe, it } from 'node:test'
|
|
6
|
+
|
|
7
|
+
const originalEnv = {
|
|
8
|
+
DATA_DIR: process.env.DATA_DIR,
|
|
9
|
+
WORKSPACE_DIR: process.env.WORKSPACE_DIR,
|
|
10
|
+
SWARMCLAW_BUILD_MODE: process.env.SWARMCLAW_BUILD_MODE,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let tempDir = ''
|
|
14
|
+
let runtime: typeof import('./subagent-runtime')
|
|
15
|
+
let lineage: typeof import('./subagent-lineage')
|
|
16
|
+
let delegationJobs: typeof import('./delegation-jobs')
|
|
17
|
+
let storage: typeof import('./storage')
|
|
18
|
+
|
|
19
|
+
before(async () => {
|
|
20
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-subagent-runtime-'))
|
|
21
|
+
process.env.DATA_DIR = path.join(tempDir, 'data')
|
|
22
|
+
process.env.WORKSPACE_DIR = path.join(tempDir, 'workspace')
|
|
23
|
+
process.env.SWARMCLAW_BUILD_MODE = '1'
|
|
24
|
+
|
|
25
|
+
storage = await import('./storage')
|
|
26
|
+
delegationJobs = await import('./delegation-jobs')
|
|
27
|
+
lineage = await import('./subagent-lineage')
|
|
28
|
+
runtime = await import('./subagent-runtime')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
after(() => {
|
|
32
|
+
if (originalEnv.DATA_DIR === undefined) delete process.env.DATA_DIR
|
|
33
|
+
else process.env.DATA_DIR = originalEnv.DATA_DIR
|
|
34
|
+
if (originalEnv.WORKSPACE_DIR === undefined) delete process.env.WORKSPACE_DIR
|
|
35
|
+
else process.env.WORKSPACE_DIR = originalEnv.WORKSPACE_DIR
|
|
36
|
+
if (originalEnv.SWARMCLAW_BUILD_MODE === undefined) delete process.env.SWARMCLAW_BUILD_MODE
|
|
37
|
+
else process.env.SWARMCLAW_BUILD_MODE = originalEnv.SWARMCLAW_BUILD_MODE
|
|
38
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
function seedAgent(id: string, name: string, plugins: string[] = []) {
|
|
42
|
+
const agents = storage.loadAgents()
|
|
43
|
+
agents[id] = {
|
|
44
|
+
id,
|
|
45
|
+
name,
|
|
46
|
+
provider: 'anthropic',
|
|
47
|
+
model: 'claude-sonnet-4-20250514',
|
|
48
|
+
systemPrompt: 'Test agent',
|
|
49
|
+
plugins,
|
|
50
|
+
}
|
|
51
|
+
storage.saveAgents(agents)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
describe('subagent-runtime', () => {
|
|
55
|
+
before(() => {
|
|
56
|
+
lineage._clearLineage()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('spawnSubagent', () => {
|
|
60
|
+
it('throws for unknown agent', () => {
|
|
61
|
+
lineage._clearLineage()
|
|
62
|
+
assert.throws(
|
|
63
|
+
() => runtime.spawnSubagent(
|
|
64
|
+
{ agentId: 'nonexistent', message: 'hello' },
|
|
65
|
+
{ cwd: tempDir },
|
|
66
|
+
),
|
|
67
|
+
/not found/,
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('throws when max depth is exceeded', () => {
|
|
72
|
+
lineage._clearLineage()
|
|
73
|
+
seedAgent('depth-agent', 'Depth Agent')
|
|
74
|
+
|
|
75
|
+
// Create a chain of sessions to simulate depth
|
|
76
|
+
const sessions = storage.loadSessions()
|
|
77
|
+
sessions['depth-s0'] = { id: 'depth-s0', parentSessionId: null, cwd: tempDir }
|
|
78
|
+
sessions['depth-s1'] = { id: 'depth-s1', parentSessionId: 'depth-s0', cwd: tempDir }
|
|
79
|
+
sessions['depth-s2'] = { id: 'depth-s2', parentSessionId: 'depth-s1', cwd: tempDir }
|
|
80
|
+
sessions['depth-s3'] = { id: 'depth-s3', parentSessionId: 'depth-s2', cwd: tempDir }
|
|
81
|
+
storage.saveSessions(sessions)
|
|
82
|
+
|
|
83
|
+
assert.throws(
|
|
84
|
+
() => runtime.spawnSubagent(
|
|
85
|
+
{ agentId: 'depth-agent', message: 'too deep' },
|
|
86
|
+
{ sessionId: 'depth-s3', cwd: tempDir },
|
|
87
|
+
),
|
|
88
|
+
/Max subagent depth/,
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('creates session, lineage node, and delegation job', () => {
|
|
93
|
+
lineage._clearLineage()
|
|
94
|
+
seedAgent('spawn-agent', 'Spawn Agent')
|
|
95
|
+
|
|
96
|
+
let handle: ReturnType<typeof runtime.spawnSubagent> | null = null
|
|
97
|
+
try {
|
|
98
|
+
handle = runtime.spawnSubagent(
|
|
99
|
+
{ agentId: 'spawn-agent', message: 'test task', waitForCompletion: false },
|
|
100
|
+
{ sessionId: undefined, cwd: tempDir },
|
|
101
|
+
)
|
|
102
|
+
} catch {
|
|
103
|
+
// May throw if enqueueSessionRun fails synchronously
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (handle) {
|
|
107
|
+
// Verify delegation job was created
|
|
108
|
+
const job = delegationJobs.getDelegationJob(handle.jobId)
|
|
109
|
+
assert.ok(job, 'Delegation job should exist')
|
|
110
|
+
assert.equal(job.kind, 'subagent')
|
|
111
|
+
assert.equal(job.agentId, 'spawn-agent')
|
|
112
|
+
|
|
113
|
+
// Verify lineage node was created with lifecycle state
|
|
114
|
+
const node = lineage.getLineageNodeBySession(handle.sessionId)
|
|
115
|
+
assert.ok(node, 'Lineage node should exist')
|
|
116
|
+
assert.equal(node.agentId, 'spawn-agent')
|
|
117
|
+
assert.equal(node.agentName, 'Spawn Agent')
|
|
118
|
+
assert.equal(node.depth, 0)
|
|
119
|
+
assert.equal(node.task, 'test task')
|
|
120
|
+
assert.equal(node.status, 'running') // initializing → ready → running
|
|
121
|
+
|
|
122
|
+
// Verify handle is registered for promise-based waiting
|
|
123
|
+
const retrieved = runtime.getHandle(handle.jobId)
|
|
124
|
+
assert.ok(retrieved, 'Handle should be registered')
|
|
125
|
+
assert.equal(retrieved.jobId, handle.jobId)
|
|
126
|
+
|
|
127
|
+
// Verify session was created
|
|
128
|
+
const sessions = storage.loadSessions()
|
|
129
|
+
assert.ok(sessions[handle.sessionId], 'Session should exist')
|
|
130
|
+
assert.equal(sessions[handle.sessionId].agentId, 'spawn-agent')
|
|
131
|
+
assert.ok(sessions[handle.sessionId].createdAt > 0)
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('tracks parent-child lineage correctly', () => {
|
|
136
|
+
lineage._clearLineage()
|
|
137
|
+
seedAgent('parent-agent', 'Parent')
|
|
138
|
+
seedAgent('child-agent', 'Child')
|
|
139
|
+
|
|
140
|
+
// Create a parent session
|
|
141
|
+
const sessions = storage.loadSessions()
|
|
142
|
+
sessions['parent-session'] = {
|
|
143
|
+
id: 'parent-session',
|
|
144
|
+
cwd: tempDir,
|
|
145
|
+
parentSessionId: null,
|
|
146
|
+
agentId: 'parent-agent',
|
|
147
|
+
}
|
|
148
|
+
storage.saveSessions(sessions)
|
|
149
|
+
|
|
150
|
+
// Create parent lineage node
|
|
151
|
+
lineage.createLineageNode({
|
|
152
|
+
sessionId: 'parent-session',
|
|
153
|
+
agentId: 'parent-agent',
|
|
154
|
+
agentName: 'Parent',
|
|
155
|
+
task: 'Parent task',
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
let handle: ReturnType<typeof runtime.spawnSubagent> | null = null
|
|
159
|
+
try {
|
|
160
|
+
handle = runtime.spawnSubagent(
|
|
161
|
+
{ agentId: 'child-agent', message: 'child task', waitForCompletion: false },
|
|
162
|
+
{ sessionId: 'parent-session', cwd: tempDir },
|
|
163
|
+
)
|
|
164
|
+
} catch {
|
|
165
|
+
// May throw from async execution
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (handle) {
|
|
169
|
+
const childNode = lineage.getLineageNodeBySession(handle.sessionId)
|
|
170
|
+
assert.ok(childNode)
|
|
171
|
+
assert.equal(childNode.depth, 1)
|
|
172
|
+
assert.equal(childNode.parentSessionId, 'parent-session')
|
|
173
|
+
assert.equal(childNode.agentName, 'Child')
|
|
174
|
+
|
|
175
|
+
// Verify parent can see child
|
|
176
|
+
const parentNode = lineage.getLineageNodeBySession('parent-session')!
|
|
177
|
+
const children = lineage.getChildren(parentNode.id)
|
|
178
|
+
assert.equal(children.length, 1)
|
|
179
|
+
assert.equal(children[0].sessionId, handle.sessionId)
|
|
180
|
+
|
|
181
|
+
// Verify ancestry
|
|
182
|
+
const ancestors = lineage.getAncestors(childNode.id)
|
|
183
|
+
assert.equal(ancestors.length, 1)
|
|
184
|
+
assert.equal(ancestors[0].sessionId, 'parent-session')
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
describe('mergePlugins', () => {
|
|
190
|
+
it('returns agent plugins when parent has none', () => {
|
|
191
|
+
const merged = runtime._mergePlugins(['shell', 'memory'], null)
|
|
192
|
+
assert.deepEqual(merged, ['shell', 'memory'])
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it('returns parent plugins when agent has none', () => {
|
|
196
|
+
const merged = runtime._mergePlugins([], { plugins: ['browser', 'web'] })
|
|
197
|
+
assert.deepEqual(merged, ['browser', 'web'])
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('merges and deduplicates agent + parent plugins', () => {
|
|
201
|
+
const merged = runtime._mergePlugins(
|
|
202
|
+
['shell', 'memory'],
|
|
203
|
+
{ plugins: ['memory', 'browser', 'web'] },
|
|
204
|
+
)
|
|
205
|
+
// agent plugins first, then parent fills gaps; 'memory' not duplicated
|
|
206
|
+
assert.deepEqual(merged, ['shell', 'memory', 'browser', 'web'])
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('deduplicates case-insensitively', () => {
|
|
210
|
+
const merged = runtime._mergePlugins(['Shell'], { plugins: ['shell', 'web'] })
|
|
211
|
+
assert.equal(merged.length, 2)
|
|
212
|
+
assert.equal(merged[0], 'Shell') // preserves original case
|
|
213
|
+
assert.equal(merged[1], 'web')
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('falls back to parent tools when plugins is missing', () => {
|
|
217
|
+
const merged = runtime._mergePlugins(['shell'], { tools: ['browser'] })
|
|
218
|
+
assert.deepEqual(merged, ['shell', 'browser'])
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('ignores empty/whitespace strings', () => {
|
|
222
|
+
const merged = runtime._mergePlugins(['shell', ''], { plugins: [' ', 'web'] })
|
|
223
|
+
assert.deepEqual(merged, ['shell', 'web'])
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it('returns empty array when both are empty', () => {
|
|
227
|
+
const merged = runtime._mergePlugins([], { plugins: [] })
|
|
228
|
+
assert.deepEqual(merged, [])
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
describe('plugin inheritance in spawnSubagent', () => {
|
|
233
|
+
it('child session inherits parent plugins merged with agent plugins', () => {
|
|
234
|
+
lineage._clearLineage()
|
|
235
|
+
seedAgent('inherit-agent', 'Inherit Agent', ['shell', 'memory'])
|
|
236
|
+
|
|
237
|
+
const sessions = storage.loadSessions()
|
|
238
|
+
sessions['inherit-parent'] = {
|
|
239
|
+
id: 'inherit-parent',
|
|
240
|
+
cwd: tempDir,
|
|
241
|
+
parentSessionId: null,
|
|
242
|
+
agentId: 'inherit-agent',
|
|
243
|
+
plugins: ['shell', 'browser', 'web', 'manage_connectors'],
|
|
244
|
+
}
|
|
245
|
+
storage.saveSessions(sessions)
|
|
246
|
+
|
|
247
|
+
let handle: ReturnType<typeof runtime.spawnSubagent> | null = null
|
|
248
|
+
try {
|
|
249
|
+
handle = runtime.spawnSubagent(
|
|
250
|
+
{ agentId: 'inherit-agent', message: 'test inheritance', waitForCompletion: false },
|
|
251
|
+
{ sessionId: 'inherit-parent', cwd: tempDir },
|
|
252
|
+
)
|
|
253
|
+
} catch { /* enqueueSessionRun may fail */ }
|
|
254
|
+
|
|
255
|
+
if (handle) {
|
|
256
|
+
const childSession = storage.loadSessions()[handle.sessionId]
|
|
257
|
+
assert.ok(childSession, 'Child session should exist')
|
|
258
|
+
const plugins = childSession.plugins as string[]
|
|
259
|
+
assert.ok(plugins.includes('shell'), 'should have shell from agent')
|
|
260
|
+
assert.ok(plugins.includes('memory'), 'should have memory from agent')
|
|
261
|
+
assert.ok(plugins.includes('browser'), 'should inherit browser from parent')
|
|
262
|
+
assert.ok(plugins.includes('web'), 'should inherit web from parent')
|
|
263
|
+
assert.ok(plugins.includes('manage_connectors'), 'should inherit manage_connectors from parent')
|
|
264
|
+
assert.equal(plugins.filter((p: string) => p.toLowerCase() === 'shell').length, 1, 'shell should not be duplicated')
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
it('child session does not inherit when inheritPlugins is false', () => {
|
|
269
|
+
lineage._clearLineage()
|
|
270
|
+
seedAgent('no-inherit-agent', 'No Inherit Agent', ['shell'])
|
|
271
|
+
|
|
272
|
+
const sessions = storage.loadSessions()
|
|
273
|
+
sessions['no-inherit-parent'] = {
|
|
274
|
+
id: 'no-inherit-parent',
|
|
275
|
+
cwd: tempDir,
|
|
276
|
+
parentSessionId: null,
|
|
277
|
+
plugins: ['shell', 'browser', 'web'],
|
|
278
|
+
}
|
|
279
|
+
storage.saveSessions(sessions)
|
|
280
|
+
|
|
281
|
+
let handle: ReturnType<typeof runtime.spawnSubagent> | null = null
|
|
282
|
+
try {
|
|
283
|
+
handle = runtime.spawnSubagent(
|
|
284
|
+
{ agentId: 'no-inherit-agent', message: 'no inherit', inheritPlugins: false, waitForCompletion: false },
|
|
285
|
+
{ sessionId: 'no-inherit-parent', cwd: tempDir },
|
|
286
|
+
)
|
|
287
|
+
} catch { /* enqueueSessionRun may fail */ }
|
|
288
|
+
|
|
289
|
+
if (handle) {
|
|
290
|
+
const childSession = storage.loadSessions()[handle.sessionId]
|
|
291
|
+
assert.ok(childSession, 'Child session should exist')
|
|
292
|
+
const plugins = childSession.plugins as string[]
|
|
293
|
+
assert.deepEqual(plugins, ['shell'], 'should only have agent plugins')
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
describe('cancelSubagentBySession', () => {
|
|
299
|
+
it('cancels a subagent and its lineage node', () => {
|
|
300
|
+
lineage._clearLineage()
|
|
301
|
+
|
|
302
|
+
const node = lineage.createLineageNode({
|
|
303
|
+
sessionId: 'cancel-session',
|
|
304
|
+
agentId: 'ag',
|
|
305
|
+
agentName: 'Cancel Agent',
|
|
306
|
+
task: 'Some task',
|
|
307
|
+
})
|
|
308
|
+
// Transition to running so it can be cancelled
|
|
309
|
+
lineage.transitionState(node.id, 'READY')
|
|
310
|
+
lineage.transitionState(node.id, 'START')
|
|
311
|
+
|
|
312
|
+
const result = runtime.cancelSubagentBySession('cancel-session')
|
|
313
|
+
assert.equal(result, true)
|
|
314
|
+
assert.equal(lineage.getLineageNode(node.id)?.status, 'cancelled')
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
it('returns false for unknown session', () => {
|
|
318
|
+
assert.equal(runtime.cancelSubagentBySession('unknown-session'), false)
|
|
319
|
+
})
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
describe('cleanupFinishedSubagents', () => {
|
|
323
|
+
it('removes old terminal lineage nodes', () => {
|
|
324
|
+
lineage._clearLineage()
|
|
325
|
+
|
|
326
|
+
const node = lineage.createLineageNode({
|
|
327
|
+
sessionId: 'cleanup-sess',
|
|
328
|
+
agentId: 'ag',
|
|
329
|
+
agentName: 'Cleanup Agent',
|
|
330
|
+
task: 'test',
|
|
331
|
+
})
|
|
332
|
+
lineage.transitionState(node.id, 'READY')
|
|
333
|
+
lineage.transitionState(node.id, 'START')
|
|
334
|
+
lineage.completeLineageNode(node.id, 'done')
|
|
335
|
+
|
|
336
|
+
// Use negative maxAge so everything qualifies as old
|
|
337
|
+
const cleaned = runtime.cleanupFinishedSubagents(-1)
|
|
338
|
+
assert.equal(cleaned, 1)
|
|
339
|
+
assert.equal(lineage.getLineageNode(node.id), null)
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
it('does not remove active lineage nodes', () => {
|
|
343
|
+
lineage._clearLineage()
|
|
344
|
+
|
|
345
|
+
const node = lineage.createLineageNode({
|
|
346
|
+
sessionId: 'active-sess',
|
|
347
|
+
agentId: 'ag',
|
|
348
|
+
agentName: 'Active Agent',
|
|
349
|
+
task: 'test',
|
|
350
|
+
})
|
|
351
|
+
lineage.transitionState(node.id, 'READY')
|
|
352
|
+
lineage.transitionState(node.id, 'START')
|
|
353
|
+
// Leave in 'running' state
|
|
354
|
+
|
|
355
|
+
const cleaned = runtime.cleanupFinishedSubagents(0)
|
|
356
|
+
assert.equal(cleaned, 0)
|
|
357
|
+
assert.ok(lineage.getLineageNode(node.id))
|
|
358
|
+
})
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
// ---------------------------------------------------------------------------
|
|
362
|
+
// Reliability fix: orphaned handle cleanup (#9)
|
|
363
|
+
// ---------------------------------------------------------------------------
|
|
364
|
+
|
|
365
|
+
describe('cleanupFinishedSubagents — orphaned handles', () => {
|
|
366
|
+
it('purges handles whose lineage node no longer exists', () => {
|
|
367
|
+
lineage._clearLineage()
|
|
368
|
+
|
|
369
|
+
// Create a lineage node and register a handle for it
|
|
370
|
+
const node = lineage.createLineageNode({
|
|
371
|
+
sessionId: 'orphan-sess',
|
|
372
|
+
agentId: 'ag',
|
|
373
|
+
agentName: 'Orphan Agent',
|
|
374
|
+
task: 'test orphan cleanup',
|
|
375
|
+
})
|
|
376
|
+
lineage.transitionState(node.id, 'READY')
|
|
377
|
+
lineage.transitionState(node.id, 'START')
|
|
378
|
+
lineage.completeLineageNode(node.id, 'done')
|
|
379
|
+
|
|
380
|
+
// Manually register a handle referencing this lineage node
|
|
381
|
+
const handleRegistry = (globalThis as any).__swarmclaw_subagent_handles__ as Map<string, any>
|
|
382
|
+
handleRegistry.set('orphan-job-1', {
|
|
383
|
+
jobId: 'orphan-job-1',
|
|
384
|
+
sessionId: 'orphan-sess',
|
|
385
|
+
lineageId: node.id,
|
|
386
|
+
agentId: 'ag',
|
|
387
|
+
agentName: 'Orphan Agent',
|
|
388
|
+
run: { runId: 'r', position: 0, promise: Promise.resolve(null), abort: () => {}, unsubscribe: () => {} },
|
|
389
|
+
promise: Promise.resolve(null),
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
// Also register a handle with a fake lineage ID that was never created
|
|
393
|
+
handleRegistry.set('orphan-job-2', {
|
|
394
|
+
jobId: 'orphan-job-2',
|
|
395
|
+
sessionId: 'never-existed',
|
|
396
|
+
lineageId: 'fake-lineage-id-xyz',
|
|
397
|
+
agentId: 'ag',
|
|
398
|
+
agentName: 'Ghost',
|
|
399
|
+
run: { runId: 'r2', position: 0, promise: Promise.resolve(null), abort: () => {}, unsubscribe: () => {} },
|
|
400
|
+
promise: Promise.resolve(null),
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
assert.equal(handleRegistry.has('orphan-job-1'), true)
|
|
404
|
+
assert.equal(handleRegistry.has('orphan-job-2'), true)
|
|
405
|
+
|
|
406
|
+
// Cleanup with negative maxAge so all terminal nodes are removed
|
|
407
|
+
const cleaned = runtime.cleanupFinishedSubagents(-1)
|
|
408
|
+
assert.equal(cleaned, 1) // one terminal node removed
|
|
409
|
+
|
|
410
|
+
// Both handles should be purged:
|
|
411
|
+
// orphan-job-1: lineage node was removed by cleanup
|
|
412
|
+
// orphan-job-2: lineage node never existed (orphaned handle)
|
|
413
|
+
assert.equal(handleRegistry.has('orphan-job-1'), false, 'Handle for cleaned lineage node should be purged')
|
|
414
|
+
assert.equal(handleRegistry.has('orphan-job-2'), false, 'Handle with non-existent lineage node should be purged')
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
it('preserves handles for active lineage nodes', () => {
|
|
418
|
+
lineage._clearLineage()
|
|
419
|
+
|
|
420
|
+
const node = lineage.createLineageNode({
|
|
421
|
+
sessionId: 'active-handle-sess',
|
|
422
|
+
agentId: 'ag',
|
|
423
|
+
agentName: 'Active',
|
|
424
|
+
task: 'still running',
|
|
425
|
+
})
|
|
426
|
+
lineage.transitionState(node.id, 'READY')
|
|
427
|
+
lineage.transitionState(node.id, 'START')
|
|
428
|
+
// Leave in running state — should NOT be cleaned up
|
|
429
|
+
|
|
430
|
+
const handleRegistry = (globalThis as any).__swarmclaw_subagent_handles__ as Map<string, any>
|
|
431
|
+
handleRegistry.set('active-job', {
|
|
432
|
+
jobId: 'active-job',
|
|
433
|
+
sessionId: 'active-handle-sess',
|
|
434
|
+
lineageId: node.id,
|
|
435
|
+
agentId: 'ag',
|
|
436
|
+
agentName: 'Active',
|
|
437
|
+
run: { runId: 'r', position: 0, promise: Promise.resolve(null), abort: () => {}, unsubscribe: () => {} },
|
|
438
|
+
promise: Promise.resolve(null),
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
runtime.cleanupFinishedSubagents(0)
|
|
442
|
+
|
|
443
|
+
assert.equal(handleRegistry.has('active-job'), true, 'Handle for active node should be preserved')
|
|
444
|
+
assert.ok(lineage.getLineageNode(node.id), 'Active lineage node should still exist')
|
|
445
|
+
})
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
describe('query helpers (re-exported)', () => {
|
|
449
|
+
it('getLineageNodeBySession works through runtime module', () => {
|
|
450
|
+
lineage._clearLineage()
|
|
451
|
+
lineage.createLineageNode({
|
|
452
|
+
sessionId: 'reexport-test',
|
|
453
|
+
agentId: 'ag',
|
|
454
|
+
agentName: 'Re-export',
|
|
455
|
+
task: 'Test',
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
const node = runtime.getLineageNodeBySession('reexport-test')
|
|
459
|
+
assert.ok(node)
|
|
460
|
+
assert.equal(node.agentName, 'Re-export')
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
it('buildLineageTree works through runtime module', () => {
|
|
464
|
+
lineage._clearLineage()
|
|
465
|
+
const root = lineage.createLineageNode({
|
|
466
|
+
sessionId: 'tree-root',
|
|
467
|
+
agentId: 'ag',
|
|
468
|
+
agentName: 'Root',
|
|
469
|
+
task: 'Root task',
|
|
470
|
+
})
|
|
471
|
+
lineage.createLineageNode({
|
|
472
|
+
sessionId: 'tree-child',
|
|
473
|
+
agentId: 'ag',
|
|
474
|
+
agentName: 'Child',
|
|
475
|
+
parentSessionId: 'tree-root',
|
|
476
|
+
task: 'Child task',
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
const tree = runtime.buildLineageTree(root.id)
|
|
480
|
+
assert.ok(tree)
|
|
481
|
+
assert.equal(tree.children.length, 1)
|
|
482
|
+
})
|
|
483
|
+
})
|
|
484
|
+
})
|