@swarmclawai/swarmclaw 0.8.5 → 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-service-timer.test.ts +6 -6
- package/src/lib/server/heartbeat-service.test.ts +406 -0
- package/src/lib/server/heartbeat-service.ts +26 -5
- 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
package/README.md
CHANGED
|
@@ -148,7 +148,7 @@ curl -fsSL https://raw.githubusercontent.com/swarmclawai/swarmclaw/main/install.
|
|
|
148
148
|
```
|
|
149
149
|
|
|
150
150
|
The installer resolves the latest stable release tag and installs that version by default.
|
|
151
|
-
To pin a version: `SWARMCLAW_VERSION=v0.8.
|
|
151
|
+
To pin a version: `SWARMCLAW_VERSION=v0.8.7 curl ... | bash`
|
|
152
152
|
|
|
153
153
|
Or run locally from the repo (friendly for non-technical users):
|
|
154
154
|
|
|
@@ -701,8 +701,8 @@ npm run update:easy # safe update helper for local installs
|
|
|
701
701
|
SwarmClaw uses tag-based releases (`vX.Y.Z`) as the stable channel.
|
|
702
702
|
|
|
703
703
|
```bash
|
|
704
|
-
# example
|
|
705
|
-
npm version
|
|
704
|
+
# example patch release (v0.8.7 style)
|
|
705
|
+
npm version patch
|
|
706
706
|
git push origin main --follow-tags
|
|
707
707
|
```
|
|
708
708
|
|
|
@@ -711,14 +711,14 @@ On `v*` tags, GitHub Actions will:
|
|
|
711
711
|
2. Create a GitHub Release
|
|
712
712
|
3. Build and publish Docker images to `ghcr.io/swarmclawai/swarmclaw` (`:vX.Y.Z`, `:latest`, `:sha-*`)
|
|
713
713
|
|
|
714
|
-
#### v0.8.
|
|
714
|
+
#### v0.8.7 Release Readiness Notes
|
|
715
715
|
|
|
716
|
-
Before shipping `v0.8.
|
|
716
|
+
Before shipping `v0.8.7`, confirm the following user-facing changes are reflected in docs:
|
|
717
717
|
|
|
718
|
-
1.
|
|
719
|
-
2.
|
|
720
|
-
3.
|
|
721
|
-
4.
|
|
718
|
+
1. Install/update docs note that `v0.8.7` repairs the committed npm lockfile so `npm ci` succeeds again on clean GitHub Actions and operator installs.
|
|
719
|
+
2. Site and README install/version strings are updated to `v0.8.7`, including install snippets, release notes index text, and sidebar/footer labels.
|
|
720
|
+
3. Release notes make it explicit that this patch is a packaging/install integrity fix on top of the `v0.8.6` runtime changes, not a new behavior rollout.
|
|
721
|
+
4. The release branch and `main` stay aligned so the shipped tag points at the same commit users see on the default branch.
|
|
722
722
|
|
|
723
723
|
## CLI
|
|
724
724
|
|
package/bin/swarmclaw.js
CHANGED
|
@@ -110,11 +110,15 @@ async function main() {
|
|
|
110
110
|
const argv = process.argv.slice(2)
|
|
111
111
|
const top = argv[0]
|
|
112
112
|
|
|
113
|
-
// Route 'server' and 'update' subcommands to CJS scripts (no TS dependency).
|
|
113
|
+
// Route 'server', 'worker', and 'update' subcommands to CJS scripts (no TS dependency).
|
|
114
114
|
if (top === 'server') {
|
|
115
115
|
require('./server-cmd.js').main()
|
|
116
116
|
return
|
|
117
117
|
}
|
|
118
|
+
if (top === 'worker') {
|
|
119
|
+
require('./worker-cmd.js').main()
|
|
120
|
+
return
|
|
121
|
+
}
|
|
118
122
|
if (top === 'update') {
|
|
119
123
|
require('./update-cmd.js').main()
|
|
120
124
|
return
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs')
|
|
5
|
+
const path = require('node:path')
|
|
6
|
+
const os = require('node:os')
|
|
7
|
+
const { spawn } = require('node:child_process')
|
|
8
|
+
|
|
9
|
+
function printHelp() {
|
|
10
|
+
const help = `
|
|
11
|
+
Usage: swarmclaw worker [options]
|
|
12
|
+
|
|
13
|
+
Starts a dedicated background worker process for SwarmClaw to process background
|
|
14
|
+
queues and tasks independently of the Next.js web application.
|
|
15
|
+
|
|
16
|
+
Options:
|
|
17
|
+
-h, --help Show this help message
|
|
18
|
+
`.trim()
|
|
19
|
+
console.log(help)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function main() {
|
|
23
|
+
const args = process.argv.slice(3)
|
|
24
|
+
for (const arg of args) {
|
|
25
|
+
if (arg === '-h' || arg === '--help') {
|
|
26
|
+
printHelp()
|
|
27
|
+
process.exit(0)
|
|
28
|
+
} else {
|
|
29
|
+
console.error(`[swarmclaw] Unknown argument: ${arg}`)
|
|
30
|
+
printHelp()
|
|
31
|
+
process.exit(1)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const SWARMCLAW_HOME = process.env.SWARMCLAW_HOME || path.join(os.homedir(), '.swarmclaw')
|
|
36
|
+
const DATA_DIR = path.join(SWARMCLAW_HOME, 'data')
|
|
37
|
+
|
|
38
|
+
process.env.DATA_DIR = DATA_DIR
|
|
39
|
+
process.env.SWARMCLAW_DAEMON_BACKGROUND_SERVICES = '1'
|
|
40
|
+
// Flag that tells Next.js NOT to start the HTTP/Websocket listener, just boot the daemon.
|
|
41
|
+
process.env.SWARMCLAW_WORKER_ONLY = '1'
|
|
42
|
+
|
|
43
|
+
console.log(`[swarmclaw] Starting dedicated background worker...`)
|
|
44
|
+
console.log(`[swarmclaw] Data directory: ${DATA_DIR}`)
|
|
45
|
+
|
|
46
|
+
// We reuse the built server.js but signal it to only run the daemon
|
|
47
|
+
const standaloneBase = path.join(SWARMCLAW_HOME, '.next', 'standalone')
|
|
48
|
+
let serverJs = path.join(standaloneBase, 'server.js')
|
|
49
|
+
|
|
50
|
+
if (!fs.existsSync(serverJs)) {
|
|
51
|
+
console.error('Standalone server.js not found. Try running: swarmclaw server --build')
|
|
52
|
+
process.exit(1)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const child = spawn(process.execPath, [serverJs], {
|
|
56
|
+
env: process.env,
|
|
57
|
+
stdio: 'inherit',
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
child.on('exit', (code) => {
|
|
61
|
+
process.exit(code || 0)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
for (const sig of ['SIGINT', 'SIGTERM']) {
|
|
65
|
+
process.on(sig, () => child.kill(sig))
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (require.main === module) {
|
|
70
|
+
main()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = { main }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.7",
|
|
4
4
|
"description": "Self-hosted AI agent orchestration dashboard — manage LLM providers, orchestrate agent swarms, schedule tasks, and bridge agents to chat platforms.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"start": "node .next/standalone/server.js",
|
|
51
51
|
"start:standalone": "node .next/standalone/server.js",
|
|
52
52
|
"smoke:browser": "node ./scripts/browser-route-smoke.mjs",
|
|
53
|
+
"smoke:browser:workbench": "node ./scripts/browser-workbench-smoke.mjs",
|
|
53
54
|
"benchmark:autonomy": "node ./scripts/benchmark-autonomy-harness.mjs",
|
|
54
55
|
"benchmark:agent-regression": "node --import tsx ./scripts/run-agent-regression-suite.ts",
|
|
55
56
|
"lint": "eslint",
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import { loadAgents, saveAgents, loadSessions,
|
|
2
|
+
import { loadAgents, saveAgents, loadSessions, logActivity, upsertStoredItem, upsertStoredItems } from '@/lib/server/storage'
|
|
3
3
|
import { normalizeProviderEndpoint } from '@/lib/openclaw-endpoint'
|
|
4
4
|
import { mutateItem, notFound, type CollectionOps } from '@/lib/server/collection-helpers'
|
|
5
|
+
import { ensureAgentThreadSession } from '@/lib/server/agent-thread-session'
|
|
5
6
|
|
|
6
7
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
-
const ops: CollectionOps<any> = { load: () => loadAgents({ includeTrashed: true }), save: saveAgents, topic: 'agents' }
|
|
8
|
+
const ops: CollectionOps<any> = { load: () => loadAgents({ includeTrashed: true }), save: saveAgents, topic: 'agents', table: 'agents' }
|
|
8
9
|
|
|
9
10
|
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
10
11
|
const { id } = await params
|
|
11
12
|
const body = await req.json()
|
|
12
13
|
const result = mutateItem(ops, id, (agent) => {
|
|
13
14
|
Object.assign(agent, body, { updatedAt: Date.now() })
|
|
15
|
+
if (Array.isArray(body.plugins) || Array.isArray(body.tools)) {
|
|
16
|
+
agent.plugins = Array.isArray(body.plugins) ? body.plugins : body.tools
|
|
17
|
+
delete (agent as Record<string, unknown>).tools
|
|
18
|
+
}
|
|
14
19
|
if (body.platformAssignScope === 'all' || body.platformAssignScope === 'self') {
|
|
15
20
|
agent.platformAssignScope = body.platformAssignScope
|
|
16
21
|
agent.isOrchestrator = body.platformAssignScope === 'all'
|
|
@@ -64,6 +69,10 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
64
69
|
})
|
|
65
70
|
if (!result) return notFound()
|
|
66
71
|
|
|
72
|
+
if (result.threadSessionId) {
|
|
73
|
+
ensureAgentThreadSession(id)
|
|
74
|
+
}
|
|
75
|
+
|
|
67
76
|
if (result.threadSessionId) {
|
|
68
77
|
const sessions = loadSessions()
|
|
69
78
|
const shortcut = sessions[result.threadSessionId]
|
|
@@ -77,7 +86,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
77
86
|
shortcut.shortcutForAgentId = id
|
|
78
87
|
changed = true
|
|
79
88
|
}
|
|
80
|
-
if (changed)
|
|
89
|
+
if (changed) upsertStoredItem('sessions', shortcut.id, shortcut)
|
|
81
90
|
}
|
|
82
91
|
}
|
|
83
92
|
|
|
@@ -97,15 +106,16 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
|
|
|
97
106
|
|
|
98
107
|
// Detach sessions from the trashed agent
|
|
99
108
|
const sessions = loadSessions()
|
|
100
|
-
|
|
109
|
+
const detached: Array<[string, unknown]> = []
|
|
101
110
|
for (const session of Object.values(sessions) as Array<Record<string, unknown>>) {
|
|
102
111
|
if (!session || session.agentId !== id) continue
|
|
103
112
|
session.agentId = null
|
|
104
|
-
|
|
113
|
+
detached.push([session.id as string, session])
|
|
105
114
|
}
|
|
106
|
-
if (
|
|
107
|
-
|
|
115
|
+
if (detached.length > 0) {
|
|
116
|
+
upsertStoredItems('sessions', detached)
|
|
108
117
|
}
|
|
118
|
+
const detachedSessions = detached.length
|
|
109
119
|
|
|
110
120
|
return NextResponse.json({ ok: true, detachedSessions })
|
|
111
121
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { genId } from '@/lib/id'
|
|
3
|
-
import {
|
|
3
|
+
import { perf } from '@/lib/server/perf'
|
|
4
|
+
import { loadAgents, loadSessions, loadUsage, logActivity, upsertStoredItem } from '@/lib/server/storage'
|
|
4
5
|
import { normalizeProviderEndpoint } from '@/lib/openclaw-endpoint'
|
|
5
6
|
import { notify } from '@/lib/server/ws-hub'
|
|
6
7
|
import { getAgentSpendWindows } from '@/lib/server/cost'
|
|
8
|
+
import { resolveAgentPluginSelection } from '@/lib/agent-default-tools'
|
|
7
9
|
import { AgentCreateSchema, formatZodError } from '@/lib/validation/schemas'
|
|
8
10
|
import { z } from 'zod'
|
|
9
11
|
export const dynamic = 'force-dynamic'
|
|
@@ -15,6 +17,7 @@ async function ensureDaemonIfNeeded(source: string) {
|
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
export async function GET(req: Request) {
|
|
20
|
+
const endPerf = perf.start('api', 'GET /api/agents')
|
|
18
21
|
const agents = loadAgents()
|
|
19
22
|
const sessions = loadSessions()
|
|
20
23
|
const usage = loadUsage()
|
|
@@ -37,28 +40,38 @@ export async function GET(req: Request) {
|
|
|
37
40
|
|
|
38
41
|
const { searchParams } = new URL(req.url)
|
|
39
42
|
const limitParam = searchParams.get('limit')
|
|
40
|
-
if (!limitParam)
|
|
43
|
+
if (!limitParam) {
|
|
44
|
+
endPerf({ count: Object.keys(agents).length })
|
|
45
|
+
return NextResponse.json(agents)
|
|
46
|
+
}
|
|
41
47
|
|
|
42
48
|
const limit = Math.max(1, Number(limitParam) || 50)
|
|
43
49
|
const offset = Math.max(0, Number(searchParams.get('offset')) || 0)
|
|
44
50
|
const all = Object.values(agents).sort((a, b) => b.updatedAt - a.updatedAt)
|
|
45
51
|
const items = all.slice(offset, offset + limit)
|
|
52
|
+
endPerf({ count: items.length, total: all.length })
|
|
46
53
|
return NextResponse.json({ items, total: all.length, hasMore: offset + limit < all.length })
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
export async function POST(req: Request) {
|
|
50
57
|
await ensureDaemonIfNeeded('api/agents:post')
|
|
51
58
|
const raw = await req.json()
|
|
59
|
+
const rawRecord = raw && typeof raw === 'object' ? raw as Record<string, unknown> : null
|
|
52
60
|
const parsed = AgentCreateSchema.safeParse(raw)
|
|
53
61
|
if (!parsed.success) {
|
|
54
62
|
return NextResponse.json(formatZodError(parsed.error as z.ZodError), { status: 400 })
|
|
55
63
|
}
|
|
56
64
|
const body = parsed.data
|
|
65
|
+
const plugins = resolveAgentPluginSelection({
|
|
66
|
+
hasExplicitPlugins: Boolean(rawRecord && Object.prototype.hasOwnProperty.call(rawRecord, 'plugins')),
|
|
67
|
+
hasExplicitTools: Boolean(rawRecord && Object.prototype.hasOwnProperty.call(rawRecord, 'tools')),
|
|
68
|
+
plugins: body.plugins,
|
|
69
|
+
tools: body.tools,
|
|
70
|
+
})
|
|
57
71
|
const id = genId()
|
|
58
72
|
const now = Date.now()
|
|
59
|
-
const agents = loadAgents()
|
|
60
73
|
const platformAssignScope = body.platformAssignScope
|
|
61
|
-
|
|
74
|
+
const agent = {
|
|
62
75
|
id,
|
|
63
76
|
name: body.name,
|
|
64
77
|
description: body.description,
|
|
@@ -80,7 +93,7 @@ export async function POST(req: Request) {
|
|
|
80
93
|
isOrchestrator: platformAssignScope === 'all',
|
|
81
94
|
platformAssignScope,
|
|
82
95
|
subAgentIds: body.subAgentIds,
|
|
83
|
-
plugins
|
|
96
|
+
plugins,
|
|
84
97
|
skills: body.skills,
|
|
85
98
|
skillIds: body.skillIds,
|
|
86
99
|
mcpServerIds: body.mcpServerIds,
|
|
@@ -113,8 +126,8 @@ export async function POST(req: Request) {
|
|
|
113
126
|
createdAt: now,
|
|
114
127
|
updatedAt: now,
|
|
115
128
|
}
|
|
116
|
-
|
|
117
|
-
logActivity({ entityType: 'agent', entityId: id, action: 'created', actor: 'user', summary: `Agent created: "${
|
|
129
|
+
upsertStoredItem('agents', id, agent)
|
|
130
|
+
logActivity({ entityType: 'agent', entityId: id, action: 'created', actor: 'user', summary: `Agent created: "${agent.name}"` })
|
|
118
131
|
notify('agents')
|
|
119
|
-
return NextResponse.json(
|
|
132
|
+
return NextResponse.json(agent)
|
|
120
133
|
}
|
|
@@ -34,9 +34,9 @@ function runWithTempDataDir(script: string) {
|
|
|
34
34
|
|
|
35
35
|
test('GET and POST /api/approvals smoke the pending approval flow end-to-end', () => {
|
|
36
36
|
const output = runWithTempDataDir(`
|
|
37
|
-
const storageMod = await import('./src/lib/server/storage
|
|
38
|
-
const approvalsMod = await import('./src/lib/server/approvals
|
|
39
|
-
const routeMod = await import('./src/app/api/approvals/route
|
|
37
|
+
const storageMod = await import('./src/lib/server/storage')
|
|
38
|
+
const approvalsMod = await import('./src/lib/server/approvals')
|
|
39
|
+
const routeMod = await import('./src/app/api/approvals/route')
|
|
40
40
|
const storage = storageMod.default || storageMod
|
|
41
41
|
const approvals = approvalsMod.default || approvalsMod
|
|
42
42
|
const route = routeMod.default || routeMod
|
|
@@ -88,9 +88,9 @@ test('GET and POST /api/approvals smoke the pending approval flow end-to-end', (
|
|
|
88
88
|
|
|
89
89
|
test('POST /api/approvals rejects invalid payloads and remains idempotent for repeated decisions', () => {
|
|
90
90
|
const output = runWithTempDataDir(`
|
|
91
|
-
const storageMod = await import('./src/lib/server/storage
|
|
92
|
-
const approvalsMod = await import('./src/lib/server/approvals
|
|
93
|
-
const routeMod = await import('./src/app/api/approvals/route
|
|
91
|
+
const storageMod = await import('./src/lib/server/storage')
|
|
92
|
+
const approvalsMod = await import('./src/lib/server/approvals')
|
|
93
|
+
const routeMod = await import('./src/app/api/approvals/route')
|
|
94
94
|
const storage = storageMod.default || storageMod
|
|
95
95
|
const approvals = approvalsMod.default || approvalsMod
|
|
96
96
|
const route = routeMod.default || routeMod
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { listPendingApprovals, submitDecision } from '@/lib/server/approvals'
|
|
3
|
+
import { errorMessage } from '@/lib/shared-utils'
|
|
3
4
|
|
|
4
5
|
export const dynamic = 'force-dynamic'
|
|
5
6
|
|
|
@@ -17,6 +18,6 @@ export async function POST(req: Request) {
|
|
|
17
18
|
await submitDecision(id, approved)
|
|
18
19
|
return NextResponse.json({ ok: true })
|
|
19
20
|
} catch (err: unknown) {
|
|
20
|
-
return NextResponse.json({ error:
|
|
21
|
+
return NextResponse.json({ error: errorMessage(err) }, { status: 500 })
|
|
21
22
|
}
|
|
22
23
|
}
|
|
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import { validateAccessKey, isFirstTimeSetup, markSetupComplete } from '@/lib/server/storage'
|
|
3
3
|
import { AUTH_COOKIE_NAME, getCookieValue } from '@/lib/auth'
|
|
4
4
|
import { isProductionRuntime } from '@/lib/runtime-env'
|
|
5
|
+
import { hmrSingleton } from '@/lib/shared-utils'
|
|
5
6
|
export const dynamic = 'force-dynamic'
|
|
6
7
|
|
|
7
8
|
interface AuthAttemptEntry {
|
|
@@ -9,9 +10,7 @@ interface AuthAttemptEntry {
|
|
|
9
10
|
lockedUntil: number
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const authRateLimitMap = (
|
|
13
|
-
(globalThis as Record<string, unknown>).__swarmclaw_auth_rate_limit__ ??= new Map()
|
|
14
|
-
) as Map<string, AuthAttemptEntry>
|
|
13
|
+
const authRateLimitMap = hmrSingleton('__swarmclaw_auth_rate_limit__', () => new Map<string, AuthAttemptEntry>())
|
|
15
14
|
|
|
16
15
|
const MAX_ATTEMPTS = 5
|
|
17
16
|
const LOCKOUT_MS = 15 * 60 * 1000
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import test from 'node:test'
|
|
3
|
+
|
|
4
|
+
import { runWithTempDataDir } from '@/lib/server/test-utils/run-with-temp-data-dir'
|
|
5
|
+
|
|
6
|
+
test('chatroom route prevents duplicate chained replies when an already-queued agent is re-mentioned', () => {
|
|
7
|
+
const output = runWithTempDataDir<{
|
|
8
|
+
assistantCounts: Record<string, number>
|
|
9
|
+
startCounts: Record<string, number>
|
|
10
|
+
doneCounts: Record<string, number>
|
|
11
|
+
messageTexts: string[]
|
|
12
|
+
}>(`
|
|
13
|
+
const storageMod = await import('./src/lib/server/storage')
|
|
14
|
+
const providersMod = await import('@/lib/providers')
|
|
15
|
+
const routeMod = await import('./src/app/api/chatrooms/[id]/chat/route')
|
|
16
|
+
const streamMod = await import('./src/lib/server/stream-agent-chat')
|
|
17
|
+
const storage = storageMod.default || storageMod
|
|
18
|
+
const providers = providersMod.default || providersMod
|
|
19
|
+
const route = routeMod.default || routeMod
|
|
20
|
+
const stream = streamMod.default || streamMod
|
|
21
|
+
|
|
22
|
+
providers.PROVIDERS['chatroom-provider'] = {
|
|
23
|
+
id: 'chatroom-provider',
|
|
24
|
+
name: 'Chatroom Provider',
|
|
25
|
+
models: ['room-model'],
|
|
26
|
+
requiresApiKey: false,
|
|
27
|
+
requiresEndpoint: false,
|
|
28
|
+
handler: { streamChat: async () => '' },
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const now = Date.now()
|
|
32
|
+
storage.saveAgents({
|
|
33
|
+
alpha: {
|
|
34
|
+
id: 'alpha',
|
|
35
|
+
name: 'Alpha',
|
|
36
|
+
provider: 'chatroom-provider',
|
|
37
|
+
model: 'room-model',
|
|
38
|
+
plugins: [],
|
|
39
|
+
createdAt: now,
|
|
40
|
+
updatedAt: now,
|
|
41
|
+
},
|
|
42
|
+
beta: {
|
|
43
|
+
id: 'beta',
|
|
44
|
+
name: 'Beta',
|
|
45
|
+
provider: 'chatroom-provider',
|
|
46
|
+
model: 'room-model',
|
|
47
|
+
plugins: [],
|
|
48
|
+
createdAt: now,
|
|
49
|
+
updatedAt: now,
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
storage.saveChatrooms({
|
|
53
|
+
room_1: {
|
|
54
|
+
id: 'room_1',
|
|
55
|
+
name: 'Workbench Room',
|
|
56
|
+
agentIds: ['alpha', 'beta'],
|
|
57
|
+
messages: [],
|
|
58
|
+
createdAt: now,
|
|
59
|
+
updatedAt: now,
|
|
60
|
+
chatMode: 'sequential',
|
|
61
|
+
autoAddress: false,
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
async function readSse(response) {
|
|
66
|
+
const reader = response.body.getReader()
|
|
67
|
+
const decoder = new TextDecoder()
|
|
68
|
+
let buffer = ''
|
|
69
|
+
const events = []
|
|
70
|
+
while (true) {
|
|
71
|
+
const { done, value } = await reader.read()
|
|
72
|
+
if (done) break
|
|
73
|
+
buffer += decoder.decode(value, { stream: true })
|
|
74
|
+
let idx = buffer.indexOf('\\n\\n')
|
|
75
|
+
while (idx !== -1) {
|
|
76
|
+
const chunk = buffer.slice(0, idx)
|
|
77
|
+
buffer = buffer.slice(idx + 2)
|
|
78
|
+
const line = chunk
|
|
79
|
+
.split('\\n')
|
|
80
|
+
.map((entry) => entry.trim())
|
|
81
|
+
.find((entry) => entry.startsWith('data: '))
|
|
82
|
+
if (line) {
|
|
83
|
+
events.push(JSON.parse(line.slice(6)))
|
|
84
|
+
}
|
|
85
|
+
idx = buffer.indexOf('\\n\\n')
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return events
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
stream.setStreamAgentChatForTest(async (opts) => {
|
|
92
|
+
const agentId = opts.session?.agentId
|
|
93
|
+
if (agentId === 'alpha') {
|
|
94
|
+
const reply = '@Beta please double-check this. Alpha found the first issue.'
|
|
95
|
+
opts.write('data: ' + JSON.stringify({ t: 'r', text: reply }) + '\\n')
|
|
96
|
+
return { fullText: reply, finalResponse: reply }
|
|
97
|
+
}
|
|
98
|
+
if (agentId === 'beta') {
|
|
99
|
+
const reply = 'Beta checked it and confirmed the fix.'
|
|
100
|
+
opts.write('data: ' + JSON.stringify({ t: 'r', text: reply }) + '\\n')
|
|
101
|
+
return { fullText: reply, finalResponse: reply }
|
|
102
|
+
}
|
|
103
|
+
return { fullText: '', finalResponse: '' }
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const response = await route.POST(
|
|
108
|
+
new Request('http://local/api/chatrooms/room_1/chat', {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: { 'content-type': 'application/json' },
|
|
111
|
+
body: JSON.stringify({ senderId: 'user', text: '@Alpha @Beta coordinate on this fix.' }),
|
|
112
|
+
}),
|
|
113
|
+
{ params: Promise.resolve({ id: 'room_1' }) },
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
const events = await readSse(response)
|
|
117
|
+
const chatroom = storage.loadChatrooms().room_1
|
|
118
|
+
const assistantMessages = chatroom.messages.filter((entry) => entry.role === 'assistant')
|
|
119
|
+
const assistantCounts = assistantMessages.reduce((acc, entry) => {
|
|
120
|
+
acc[entry.senderId] = (acc[entry.senderId] || 0) + 1
|
|
121
|
+
return acc
|
|
122
|
+
}, {})
|
|
123
|
+
const startCounts = events
|
|
124
|
+
.filter((entry) => entry.t === 'cr_agent_start')
|
|
125
|
+
.reduce((acc, entry) => {
|
|
126
|
+
acc[entry.agentId] = (acc[entry.agentId] || 0) + 1
|
|
127
|
+
return acc
|
|
128
|
+
}, {})
|
|
129
|
+
const doneCounts = events
|
|
130
|
+
.filter((entry) => entry.t === 'cr_agent_done')
|
|
131
|
+
.reduce((acc, entry) => {
|
|
132
|
+
acc[entry.agentId] = (acc[entry.agentId] || 0) + 1
|
|
133
|
+
return acc
|
|
134
|
+
}, {})
|
|
135
|
+
|
|
136
|
+
console.log(JSON.stringify({
|
|
137
|
+
assistantCounts,
|
|
138
|
+
startCounts,
|
|
139
|
+
doneCounts,
|
|
140
|
+
messageTexts: assistantMessages.map((entry) => entry.text),
|
|
141
|
+
}))
|
|
142
|
+
} finally {
|
|
143
|
+
stream.setStreamAgentChatForTest(null)
|
|
144
|
+
}
|
|
145
|
+
`, { prefix: 'swarmclaw-chatroom-route-test-' })
|
|
146
|
+
|
|
147
|
+
assert.deepEqual(output.assistantCounts, { alpha: 1, beta: 1 })
|
|
148
|
+
assert.deepEqual(output.startCounts, { alpha: 1, beta: 1 })
|
|
149
|
+
assert.deepEqual(output.doneCounts, { alpha: 1, beta: 1 })
|
|
150
|
+
assert.equal(output.messageTexts.some((text) => /double-check/i.test(text)), true)
|
|
151
|
+
assert.equal(output.messageTexts.some((text) => /confirmed the fix/i.test(text)), true)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test('chatroom route forwards tool activity and records one reply per participating agent', () => {
|
|
155
|
+
const output = runWithTempDataDir<{
|
|
156
|
+
toolCalls: string[]
|
|
157
|
+
toolResults: string[]
|
|
158
|
+
assistantCounts: Record<string, number>
|
|
159
|
+
agentOrder: string[]
|
|
160
|
+
}>(`
|
|
161
|
+
const storageMod = await import('./src/lib/server/storage')
|
|
162
|
+
const providersMod = await import('@/lib/providers')
|
|
163
|
+
const routeMod = await import('./src/app/api/chatrooms/[id]/chat/route')
|
|
164
|
+
const streamMod = await import('./src/lib/server/stream-agent-chat')
|
|
165
|
+
const storage = storageMod.default || storageMod
|
|
166
|
+
const providers = providersMod.default || providersMod
|
|
167
|
+
const route = routeMod.default || routeMod
|
|
168
|
+
const stream = streamMod.default || streamMod
|
|
169
|
+
|
|
170
|
+
providers.PROVIDERS['chatroom-tool-provider'] = {
|
|
171
|
+
id: 'chatroom-tool-provider',
|
|
172
|
+
name: 'Chatroom Tool Provider',
|
|
173
|
+
models: ['room-tool-model'],
|
|
174
|
+
requiresApiKey: false,
|
|
175
|
+
requiresEndpoint: false,
|
|
176
|
+
handler: { streamChat: async () => '' },
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const now = Date.now()
|
|
180
|
+
storage.saveAgents({
|
|
181
|
+
alpha: {
|
|
182
|
+
id: 'alpha',
|
|
183
|
+
name: 'Alpha',
|
|
184
|
+
provider: 'chatroom-tool-provider',
|
|
185
|
+
model: 'room-tool-model',
|
|
186
|
+
plugins: ['shell'],
|
|
187
|
+
createdAt: now,
|
|
188
|
+
updatedAt: now,
|
|
189
|
+
},
|
|
190
|
+
beta: {
|
|
191
|
+
id: 'beta',
|
|
192
|
+
name: 'Beta',
|
|
193
|
+
provider: 'chatroom-tool-provider',
|
|
194
|
+
model: 'room-tool-model',
|
|
195
|
+
plugins: ['shell'],
|
|
196
|
+
createdAt: now,
|
|
197
|
+
updatedAt: now,
|
|
198
|
+
},
|
|
199
|
+
})
|
|
200
|
+
storage.saveChatrooms({
|
|
201
|
+
room_1: {
|
|
202
|
+
id: 'room_1',
|
|
203
|
+
name: 'Parallel Workbench Room',
|
|
204
|
+
agentIds: ['alpha', 'beta'],
|
|
205
|
+
messages: [],
|
|
206
|
+
createdAt: now,
|
|
207
|
+
updatedAt: now,
|
|
208
|
+
chatMode: 'parallel',
|
|
209
|
+
autoAddress: true,
|
|
210
|
+
},
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
async function readSse(response) {
|
|
214
|
+
const reader = response.body.getReader()
|
|
215
|
+
const decoder = new TextDecoder()
|
|
216
|
+
let buffer = ''
|
|
217
|
+
const events = []
|
|
218
|
+
while (true) {
|
|
219
|
+
const { done, value } = await reader.read()
|
|
220
|
+
if (done) break
|
|
221
|
+
buffer += decoder.decode(value, { stream: true })
|
|
222
|
+
let idx = buffer.indexOf('\\n\\n')
|
|
223
|
+
while (idx !== -1) {
|
|
224
|
+
const chunk = buffer.slice(0, idx)
|
|
225
|
+
buffer = buffer.slice(idx + 2)
|
|
226
|
+
const line = chunk
|
|
227
|
+
.split('\\n')
|
|
228
|
+
.map((entry) => entry.trim())
|
|
229
|
+
.find((entry) => entry.startsWith('data: '))
|
|
230
|
+
if (line) {
|
|
231
|
+
events.push(JSON.parse(line.slice(6)))
|
|
232
|
+
}
|
|
233
|
+
idx = buffer.indexOf('\\n\\n')
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return events
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
stream.setStreamAgentChatForTest(async (opts) => {
|
|
240
|
+
const agentId = opts.session?.agentId
|
|
241
|
+
if (agentId === 'alpha') {
|
|
242
|
+
opts.write('data: ' + JSON.stringify({
|
|
243
|
+
t: 'tool_call',
|
|
244
|
+
toolName: 'shell',
|
|
245
|
+
toolInput: 'pwd',
|
|
246
|
+
toolCallId: 'alpha-shell',
|
|
247
|
+
}) + '\\n')
|
|
248
|
+
opts.write('data: ' + JSON.stringify({
|
|
249
|
+
t: 'tool_result',
|
|
250
|
+
toolName: 'shell',
|
|
251
|
+
toolOutput: process.env.WORKSPACE_DIR,
|
|
252
|
+
toolCallId: 'alpha-shell',
|
|
253
|
+
}) + '\\n')
|
|
254
|
+
const reply = 'Alpha inspected the workspace root and found the repo is ready.'
|
|
255
|
+
opts.write('data: ' + JSON.stringify({ t: 'r', text: reply }) + '\\n')
|
|
256
|
+
return { fullText: reply, finalResponse: reply }
|
|
257
|
+
}
|
|
258
|
+
if (agentId === 'beta') {
|
|
259
|
+
const reply = 'Beta reviewed the plugin path and agrees with Alpha.'
|
|
260
|
+
opts.write('data: ' + JSON.stringify({ t: 'r', text: reply }) + '\\n')
|
|
261
|
+
return { fullText: reply, finalResponse: reply }
|
|
262
|
+
}
|
|
263
|
+
return { fullText: '', finalResponse: '' }
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const response = await route.POST(
|
|
268
|
+
new Request('http://local/api/chatrooms/room_1/chat', {
|
|
269
|
+
method: 'POST',
|
|
270
|
+
headers: { 'content-type': 'application/json' },
|
|
271
|
+
body: JSON.stringify({ senderId: 'user', text: 'Please inspect the workspace and plugin path.' }),
|
|
272
|
+
}),
|
|
273
|
+
{ params: Promise.resolve({ id: 'room_1' }) },
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
const events = await readSse(response)
|
|
277
|
+
const chatroom = storage.loadChatrooms().room_1
|
|
278
|
+
const assistantMessages = chatroom.messages.filter((entry) => entry.role === 'assistant')
|
|
279
|
+
const assistantCounts = assistantMessages.reduce((acc, entry) => {
|
|
280
|
+
acc[entry.senderId] = (acc[entry.senderId] || 0) + 1
|
|
281
|
+
return acc
|
|
282
|
+
}, {})
|
|
283
|
+
|
|
284
|
+
console.log(JSON.stringify({
|
|
285
|
+
toolCalls: events.filter((entry) => entry.t === 'tool_call').map((entry) => entry.toolName),
|
|
286
|
+
toolResults: events.filter((entry) => entry.t === 'tool_result').map((entry) => entry.toolOutput),
|
|
287
|
+
assistantCounts,
|
|
288
|
+
agentOrder: assistantMessages.map((entry) => entry.senderId),
|
|
289
|
+
}))
|
|
290
|
+
} finally {
|
|
291
|
+
stream.setStreamAgentChatForTest(null)
|
|
292
|
+
}
|
|
293
|
+
`, { prefix: 'swarmclaw-chatroom-route-tools-' })
|
|
294
|
+
|
|
295
|
+
assert.deepEqual(output.toolCalls, ['shell'])
|
|
296
|
+
assert.equal(output.toolResults.length, 1)
|
|
297
|
+
assert.deepEqual(output.assistantCounts, { alpha: 1, beta: 1 })
|
|
298
|
+
assert.deepEqual([...new Set(output.agentOrder)].sort(), ['alpha', 'beta'])
|
|
299
|
+
})
|