@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { loadQueue, loadSchedules, loadSessions,
|
|
1
|
+
import { loadQueue, loadSchedules, loadSessions, loadConnectors, saveConnectors, loadWebhookRetryQueue, upsertWebhookRetry, deleteWebhookRetry, loadWebhooks, loadAgents, loadSettings, appendWebhookLog, loadCredentials, decryptKey } from './storage'
|
|
2
2
|
import { notify } from './ws-hub'
|
|
3
3
|
import { processNext, cleanupFinishedTaskSessions, validateCompletedTasksQueue, recoverStalledRunningTasks, resumeQueue } from './queue'
|
|
4
4
|
import { startScheduler, stopScheduler } from './scheduler'
|
|
@@ -18,24 +18,37 @@ import {
|
|
|
18
18
|
getReconnectState,
|
|
19
19
|
setReconnectState,
|
|
20
20
|
} from './connectors/manager'
|
|
21
|
-
import {
|
|
21
|
+
import { startConnectorOutboxWorker, stopConnectorOutboxWorker } from './connectors/outbox'
|
|
22
|
+
import { startHeartbeatService, stopHeartbeatService, getHeartbeatServiceStatus, pruneHeartbeatState } from './heartbeat-service'
|
|
22
23
|
import { hasOpenClawAgents, ensureGatewayConnected, disconnectGateway, getGateway } from './openclaw-gateway'
|
|
23
24
|
import { enqueueSessionRun } from './session-run-manager'
|
|
24
25
|
import { WORKSPACE_DIR } from './data-dir'
|
|
25
26
|
import { DEFAULT_HEARTBEAT_INTERVAL_SEC } from '@/lib/heartbeat-defaults'
|
|
26
27
|
import { genId } from '@/lib/id'
|
|
27
|
-
import {
|
|
28
|
+
import { errorMessage, hmrSingleton } from '@/lib/shared-utils'
|
|
28
29
|
import path from 'node:path'
|
|
29
30
|
import type { Session, WebhookRetryEntry } from '@/types'
|
|
30
31
|
import { createNotification } from '@/lib/server/create-notification'
|
|
31
32
|
import { pingProvider, OPENAI_COMPATIBLE_DEFAULTS } from '@/lib/server/provider-health'
|
|
32
33
|
import { runIntegrityMonitor } from '@/lib/server/integrity-monitor'
|
|
33
34
|
import { recoverStaleDelegationJobs } from './delegation-jobs'
|
|
35
|
+
import { pruneMainLoopState } from './main-agent-loop'
|
|
36
|
+
import { sweepManagedProcesses } from './process-manager'
|
|
34
37
|
import {
|
|
35
38
|
listPendingApprovalsNeedingConnectorNotification,
|
|
36
39
|
markApprovalConnectorNotificationAttempt,
|
|
37
40
|
markApprovalConnectorNotificationSent,
|
|
38
41
|
} from './approvals'
|
|
42
|
+
import {
|
|
43
|
+
buildSessionHeartbeatHealthDedupKey,
|
|
44
|
+
daemonAutostartEnvEnabled,
|
|
45
|
+
isDaemonBackgroundServicesEnabled,
|
|
46
|
+
parseCronToMs,
|
|
47
|
+
parseHeartbeatIntervalSec,
|
|
48
|
+
shouldNotifyProviderReachabilityIssue,
|
|
49
|
+
shouldSuppressSessionHeartbeatHealthAlert,
|
|
50
|
+
shouldSuppressSyntheticAgentHealthAlert,
|
|
51
|
+
} from './daemon-policy'
|
|
39
52
|
|
|
40
53
|
const QUEUE_CHECK_INTERVAL = 30_000 // 30 seconds
|
|
41
54
|
const BROWSER_SWEEP_INTERVAL = 60_000 // 60 seconds
|
|
@@ -52,74 +65,16 @@ const CONNECTOR_RESTART_BASE_MS = 30_000
|
|
|
52
65
|
const CONNECTOR_RESTART_MAX_MS = 15 * 60 * 1000
|
|
53
66
|
const MAX_WAKE_ATTEMPTS = 3
|
|
54
67
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (['0', 'false', 'no', 'off'].includes(normalized)) return false
|
|
62
|
-
return fallback
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function daemonAutostartEnvEnabled(): boolean {
|
|
66
|
-
return parseBoolish(process.env.SWARMCLAW_DAEMON_AUTOSTART, isProductionRuntime())
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function isDaemonBackgroundServicesEnabled(): boolean {
|
|
70
|
-
return parseBoolish(process.env.SWARMCLAW_DAEMON_BACKGROUND_SERVICES, true)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function parseHeartbeatIntervalSec(value: unknown, fallback = DEFAULT_HEARTBEAT_INTERVAL_SEC): number {
|
|
74
|
-
const parsed = typeof value === 'number'
|
|
75
|
-
? value
|
|
76
|
-
: typeof value === 'string'
|
|
77
|
-
? Number.parseInt(value, 10)
|
|
78
|
-
: Number.NaN
|
|
79
|
-
if (!Number.isFinite(parsed)) return fallback
|
|
80
|
-
return Math.max(0, Math.min(3600, Math.trunc(parsed)))
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function shouldNotifyProviderReachabilityIssue(provider: string): boolean {
|
|
84
|
-
return provider !== 'openclaw'
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const SYNTHETIC_HEALTH_SESSION_USERS = new Set(['workbench', 'comparison-bench'])
|
|
88
|
-
const SYNTHETIC_HEALTH_SESSION_PREFIXES = ['wb-', 'cmp-']
|
|
89
|
-
|
|
90
|
-
function hasSyntheticHealthPrefix(value: unknown): boolean {
|
|
91
|
-
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : ''
|
|
92
|
-
return SYNTHETIC_HEALTH_SESSION_PREFIXES.some((prefix) => normalized.startsWith(prefix))
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function shouldSuppressSessionHeartbeatHealthAlert(
|
|
96
|
-
session: Pick<Session, 'id' | 'name' | 'user' | 'shortcutForAgentId'>,
|
|
97
|
-
): boolean {
|
|
98
|
-
const user = typeof session.user === 'string' ? session.user.trim().toLowerCase() : ''
|
|
99
|
-
if (SYNTHETIC_HEALTH_SESSION_USERS.has(user)) return true
|
|
100
|
-
if (hasSyntheticHealthPrefix(session.id)) return true
|
|
101
|
-
if (hasSyntheticHealthPrefix(session.shortcutForAgentId)) return true
|
|
102
|
-
|
|
103
|
-
const name = typeof session.name === 'string' ? session.name.trim().toLowerCase() : ''
|
|
104
|
-
return name.startsWith('workbench ')
|
|
105
|
-
|| name.startsWith('assistant benchmark ')
|
|
106
|
-
|| name.startsWith('comparison ')
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function shouldSuppressSyntheticAgentHealthAlert(agentId: string): boolean {
|
|
110
|
-
return hasSyntheticHealthPrefix(agentId)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function buildSessionHeartbeatHealthDedupKey(
|
|
114
|
-
sessionId: string,
|
|
115
|
-
state: 'stale' | 'auto-disabled',
|
|
116
|
-
): string {
|
|
117
|
-
return `health-alert:session-heartbeat:${state}:${sessionId}`
|
|
68
|
+
export {
|
|
69
|
+
buildSessionHeartbeatHealthDedupKey,
|
|
70
|
+
isDaemonBackgroundServicesEnabled,
|
|
71
|
+
shouldNotifyProviderReachabilityIssue,
|
|
72
|
+
shouldSuppressSessionHeartbeatHealthAlert,
|
|
73
|
+
shouldSuppressSyntheticAgentHealthAlert,
|
|
118
74
|
}
|
|
119
75
|
|
|
120
76
|
// Store daemon state on globalThis to survive HMR reloads
|
|
121
|
-
|
|
122
|
-
const ds: {
|
|
77
|
+
interface DaemonState {
|
|
123
78
|
queueIntervalId: ReturnType<typeof setInterval> | null
|
|
124
79
|
browserSweepId: ReturnType<typeof setInterval> | null
|
|
125
80
|
healthIntervalId: ReturnType<typeof setInterval> | null
|
|
@@ -138,8 +93,9 @@ const ds: {
|
|
|
138
93
|
manualStopRequested: boolean
|
|
139
94
|
running: boolean
|
|
140
95
|
lastProcessedAt: number | null
|
|
141
|
-
|
|
142
|
-
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const ds: DaemonState = hmrSingleton<DaemonState>('__swarmclaw_daemon__', () => ({
|
|
143
99
|
queueIntervalId: null,
|
|
144
100
|
browserSweepId: null,
|
|
145
101
|
healthIntervalId: null,
|
|
@@ -155,7 +111,7 @@ const ds: {
|
|
|
155
111
|
manualStopRequested: false,
|
|
156
112
|
running: false,
|
|
157
113
|
lastProcessedAt: null,
|
|
158
|
-
})
|
|
114
|
+
}))
|
|
159
115
|
|
|
160
116
|
// Backfill fields for hot-reloaded daemon state objects from older code versions.
|
|
161
117
|
if (!ds.staleSessionIds) ds.staleSessionIds = new Set<string>()
|
|
@@ -193,7 +149,7 @@ export function startDaemon(options?: { source?: string; manualStart?: boolean }
|
|
|
193
149
|
startBrowserSweep()
|
|
194
150
|
startHeartbeatService()
|
|
195
151
|
startMemoryConsolidation()
|
|
196
|
-
syncDaemonBackgroundServices()
|
|
152
|
+
syncDaemonBackgroundServices({ runConnectorHealthCheckImmediately: false })
|
|
197
153
|
return
|
|
198
154
|
}
|
|
199
155
|
ds.running = true
|
|
@@ -210,18 +166,18 @@ export function startDaemon(options?: { source?: string; manualStart?: boolean }
|
|
|
210
166
|
startBrowserSweep()
|
|
211
167
|
startHeartbeatService()
|
|
212
168
|
startMemoryConsolidation()
|
|
213
|
-
syncDaemonBackgroundServices()
|
|
169
|
+
syncDaemonBackgroundServices({ runConnectorHealthCheckImmediately: false })
|
|
214
170
|
} catch (err: unknown) {
|
|
215
171
|
ds.running = false
|
|
216
172
|
notify('daemon')
|
|
217
|
-
console.error('[daemon] Failed to start:',
|
|
173
|
+
console.error('[daemon] Failed to start:', errorMessage(err))
|
|
218
174
|
throw err
|
|
219
175
|
}
|
|
220
176
|
|
|
221
177
|
if (isDaemonBackgroundServicesEnabled()) {
|
|
222
178
|
// Auto-start enabled connectors only when the full background stack is enabled.
|
|
223
179
|
autoStartConnectors().catch((err: unknown) => {
|
|
224
|
-
console.error('[daemon] Error auto-starting connectors:',
|
|
180
|
+
console.error('[daemon] Error auto-starting connectors:', errorMessage(err))
|
|
225
181
|
})
|
|
226
182
|
}
|
|
227
183
|
}
|
|
@@ -239,10 +195,11 @@ export function stopDaemon(options?: { source?: string; manualStop?: boolean })
|
|
|
239
195
|
stopBrowserSweep()
|
|
240
196
|
stopHealthMonitor()
|
|
241
197
|
stopConnectorHealthMonitor()
|
|
198
|
+
stopConnectorOutboxWorker()
|
|
242
199
|
stopHeartbeatService()
|
|
243
200
|
stopMemoryConsolidation()
|
|
244
201
|
stopEvalScheduler()
|
|
245
|
-
stopAllConnectors().catch(() => {})
|
|
202
|
+
stopAllConnectors({ disable: false }).catch(() => {})
|
|
246
203
|
}
|
|
247
204
|
|
|
248
205
|
function startBrowserSweep() {
|
|
@@ -270,6 +227,7 @@ function stopBrowserSweep() {
|
|
|
270
227
|
function startQueueProcessor() {
|
|
271
228
|
if (ds.queueIntervalId) return
|
|
272
229
|
ds.queueIntervalId = setInterval(async () => {
|
|
230
|
+
if (!ds.running) return
|
|
273
231
|
const queue = loadQueue()
|
|
274
232
|
if (queue.length > 0) {
|
|
275
233
|
console.log(`[daemon] Processing ${queue.length} queued task(s)`)
|
|
@@ -323,7 +281,7 @@ async function runConnectorHealthChecks(now: number) {
|
|
|
323
281
|
try {
|
|
324
282
|
await checkConnectorHealth()
|
|
325
283
|
} catch (err: unknown) {
|
|
326
|
-
console.error('[health] Connector isAlive check failed:',
|
|
284
|
+
console.error('[health] Connector isAlive check failed:', errorMessage(err))
|
|
327
285
|
}
|
|
328
286
|
|
|
329
287
|
const connectors = loadConnectors()
|
|
@@ -369,7 +327,7 @@ async function runConnectorHealthChecks(now: number) {
|
|
|
369
327
|
clearReconnectState(connector.id)
|
|
370
328
|
await sendHealthAlert(`Connector "${connector.name}" (${connector.platform}) was down and has been auto-restarted.`)
|
|
371
329
|
} catch (err: unknown) {
|
|
372
|
-
const message =
|
|
330
|
+
const message = errorMessage(err)
|
|
373
331
|
const next = advanceConnectorReconnectState(current, message, now, {
|
|
374
332
|
initialBackoffMs: CONNECTOR_RESTART_BASE_MS,
|
|
375
333
|
maxBackoffMs: CONNECTOR_RESTART_MAX_MS,
|
|
@@ -474,9 +432,8 @@ async function processWebhookRetries() {
|
|
|
474
432
|
heartbeatEnabled: (agent.heartbeatEnabled as boolean | undefined) ?? false,
|
|
475
433
|
heartbeatIntervalSec: (agent.heartbeatIntervalSec as number | null | undefined) ?? null,
|
|
476
434
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
save(sessions)
|
|
435
|
+
const { upsertSession: upsert } = await import('./storage')
|
|
436
|
+
upsert(session.id as string, session)
|
|
480
437
|
}
|
|
481
438
|
|
|
482
439
|
const payloadPreview = (entry.payload || '').slice(0, 12_000)
|
|
@@ -518,7 +475,7 @@ async function processWebhookRetries() {
|
|
|
518
475
|
deleteWebhookRetry(entry.id)
|
|
519
476
|
console.log(`[webhook-retry] Successfully retried ${entry.id} for webhook ${entry.webhookId} (attempt ${entry.attempts})`)
|
|
520
477
|
} catch (err: unknown) {
|
|
521
|
-
const errorMsg =
|
|
478
|
+
const errorMsg = errorMessage(err)
|
|
522
479
|
entry.attempts += 1
|
|
523
480
|
|
|
524
481
|
if (entry.attempts >= entry.maxAttempts) {
|
|
@@ -699,7 +656,7 @@ async function runOpenClawGatewayHealthChecks() {
|
|
|
699
656
|
const { runOpenClawDoctor } = await import('./openclaw-doctor')
|
|
700
657
|
await runOpenClawDoctor({ fix: true })
|
|
701
658
|
} catch (err: unknown) {
|
|
702
|
-
console.warn('[daemon] openclaw doctor --fix failed:',
|
|
659
|
+
console.warn('[daemon] openclaw doctor --fix failed:', errorMessage(err))
|
|
703
660
|
}
|
|
704
661
|
repair.attempts += 1
|
|
705
662
|
repair.lastAttemptAt = now
|
|
@@ -750,7 +707,7 @@ async function runPendingApprovalConnectorNotifications(now: number) {
|
|
|
750
707
|
entityId: reminder.approvalId,
|
|
751
708
|
})
|
|
752
709
|
} catch (err: unknown) {
|
|
753
|
-
const errorMsg =
|
|
710
|
+
const errorMsg = errorMessage(err)
|
|
754
711
|
markApprovalConnectorNotificationAttempt(reminder.approvalId, {
|
|
755
712
|
at: now,
|
|
756
713
|
connectorId: reminder.connectorId,
|
|
@@ -763,6 +720,33 @@ async function runPendingApprovalConnectorNotifications(now: number) {
|
|
|
763
720
|
}
|
|
764
721
|
}
|
|
765
722
|
|
|
723
|
+
/**
|
|
724
|
+
* Prune orphaned entries from module-level Maps/Sets that reference
|
|
725
|
+
* sessions, connectors, or agents that no longer exist in storage.
|
|
726
|
+
* Runs every health-check cycle (2 minutes).
|
|
727
|
+
*/
|
|
728
|
+
function pruneOrphanedState(sessions: Record<string, unknown>): void {
|
|
729
|
+
const liveSessionIds = new Set(Object.keys(sessions))
|
|
730
|
+
|
|
731
|
+
// Main-loop state map (per-session autonomous state)
|
|
732
|
+
pruneMainLoopState(liveSessionIds)
|
|
733
|
+
|
|
734
|
+
// Heartbeat service tracking maps
|
|
735
|
+
pruneHeartbeatState(liveSessionIds)
|
|
736
|
+
|
|
737
|
+
// Process manager — sweep completed processes older than TTL
|
|
738
|
+
sweepManagedProcesses()
|
|
739
|
+
|
|
740
|
+
// Daemon-local: prune openclawRepairState for agents that no longer exist
|
|
741
|
+
const agents = loadAgents()
|
|
742
|
+
for (const agentId of ds.openclawRepairState.keys()) {
|
|
743
|
+
if (!agents[agentId]) ds.openclawRepairState.delete(agentId)
|
|
744
|
+
}
|
|
745
|
+
for (const agentId of ds.openclawDownAgentIds) {
|
|
746
|
+
if (!agents[agentId]) ds.openclawDownAgentIds.delete(agentId)
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
766
750
|
async function runHealthChecks() {
|
|
767
751
|
// Continuously keep the completed queue honest.
|
|
768
752
|
validateCompletedTasksQueue()
|
|
@@ -774,7 +758,7 @@ async function runHealthChecks() {
|
|
|
774
758
|
const sessions = loadSessions()
|
|
775
759
|
const now = Date.now()
|
|
776
760
|
const currentlyStale = new Set<string>()
|
|
777
|
-
|
|
761
|
+
const dirtySessionIds: string[] = []
|
|
778
762
|
|
|
779
763
|
for (const session of Object.values(sessions) as Record<string, unknown>[]) {
|
|
780
764
|
if (!session?.id || typeof session.id !== 'string') continue
|
|
@@ -799,7 +783,7 @@ async function runHealthChecks() {
|
|
|
799
783
|
if (staleForMs > autoDisableAfter) {
|
|
800
784
|
session.heartbeatEnabled = false
|
|
801
785
|
session.lastActiveAt = now
|
|
802
|
-
|
|
786
|
+
dirtySessionIds.push(sessionId)
|
|
803
787
|
ds.staleSessionIds.delete(sessionId)
|
|
804
788
|
await sendHealthAlert({
|
|
805
789
|
text: `Auto-disabled heartbeat for stale session "${sessionLabel}" after ${Math.round(staleForMs / 60_000)}m of inactivity.`,
|
|
@@ -831,26 +815,32 @@ async function runHealthChecks() {
|
|
|
831
815
|
}
|
|
832
816
|
}
|
|
833
817
|
|
|
834
|
-
|
|
818
|
+
for (const sid of dirtySessionIds) {
|
|
819
|
+
const s = sessions[sid]
|
|
820
|
+
if (s) {
|
|
821
|
+
const { upsertSession: upsert } = await import('./storage')
|
|
822
|
+
upsert(sid, s)
|
|
823
|
+
}
|
|
824
|
+
}
|
|
835
825
|
|
|
836
826
|
// Provider reachability checks
|
|
837
827
|
try {
|
|
838
828
|
await runProviderHealthChecks()
|
|
839
829
|
} catch (err: unknown) {
|
|
840
|
-
console.error('[daemon] Provider health check failed:',
|
|
830
|
+
console.error('[daemon] Provider health check failed:', errorMessage(err))
|
|
841
831
|
}
|
|
842
832
|
|
|
843
833
|
// OpenClaw gateway health checks + auto-repair
|
|
844
834
|
try {
|
|
845
835
|
await runOpenClawGatewayHealthChecks()
|
|
846
836
|
} catch (err: unknown) {
|
|
847
|
-
console.error('[daemon] OpenClaw gateway health check failed:',
|
|
837
|
+
console.error('[daemon] OpenClaw gateway health check failed:', errorMessage(err))
|
|
848
838
|
}
|
|
849
839
|
|
|
850
840
|
try {
|
|
851
841
|
await runPendingApprovalConnectorNotifications(now)
|
|
852
842
|
} catch (err: unknown) {
|
|
853
|
-
console.error('[daemon] Approval connector reminder check failed:',
|
|
843
|
+
console.error('[daemon] Approval connector reminder check failed:', errorMessage(err))
|
|
854
844
|
}
|
|
855
845
|
|
|
856
846
|
// Integrity drift monitoring for identity/config/plugin files.
|
|
@@ -879,14 +869,21 @@ async function runHealthChecks() {
|
|
|
879
869
|
await sendHealthAlert(`Integrity monitor detected ${integrity.drifts.length} file drift event(s).`)
|
|
880
870
|
}
|
|
881
871
|
} catch (err: unknown) {
|
|
882
|
-
console.error('[daemon] Integrity monitor check failed:',
|
|
872
|
+
console.error('[daemon] Integrity monitor check failed:', errorMessage(err))
|
|
883
873
|
}
|
|
884
874
|
|
|
885
875
|
// Process webhook retry queue
|
|
886
876
|
try {
|
|
887
877
|
await processWebhookRetries()
|
|
888
878
|
} catch (err: unknown) {
|
|
889
|
-
console.error('[daemon] Webhook retry processing failed:',
|
|
879
|
+
console.error('[daemon] Webhook retry processing failed:', errorMessage(err))
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// Periodic memory hygiene: prune orphaned state for deleted sessions/connectors
|
|
883
|
+
try {
|
|
884
|
+
pruneOrphanedState(sessions)
|
|
885
|
+
} catch (err: unknown) {
|
|
886
|
+
console.error('[daemon] Memory hygiene sweep failed:', errorMessage(err))
|
|
890
887
|
}
|
|
891
888
|
}
|
|
892
889
|
|
|
@@ -906,28 +903,32 @@ function stopHealthMonitor() {
|
|
|
906
903
|
}
|
|
907
904
|
}
|
|
908
905
|
|
|
909
|
-
function syncDaemonBackgroundServices() {
|
|
906
|
+
function syncDaemonBackgroundServices(options?: { runConnectorHealthCheckImmediately?: boolean }) {
|
|
910
907
|
if (isDaemonBackgroundServicesEnabled()) {
|
|
911
908
|
startHealthMonitor()
|
|
912
|
-
startConnectorHealthMonitor(
|
|
909
|
+
startConnectorHealthMonitor({
|
|
910
|
+
runImmediately: options?.runConnectorHealthCheckImmediately !== false,
|
|
911
|
+
})
|
|
912
|
+
startConnectorOutboxWorker()
|
|
913
913
|
startEvalScheduler()
|
|
914
914
|
return
|
|
915
915
|
}
|
|
916
916
|
stopHealthMonitor()
|
|
917
917
|
stopConnectorHealthMonitor()
|
|
918
|
+
stopConnectorOutboxWorker()
|
|
918
919
|
stopEvalScheduler()
|
|
919
920
|
}
|
|
920
921
|
|
|
921
|
-
function startConnectorHealthMonitor() {
|
|
922
|
+
function startConnectorHealthMonitor(options?: { runImmediately?: boolean }) {
|
|
922
923
|
if (ds.connectorHealthIntervalId) return
|
|
923
924
|
|
|
924
925
|
const tick = () => {
|
|
925
926
|
runConnectorHealthChecks(Date.now()).catch((err) => {
|
|
926
|
-
console.error('[daemon] Connector health tick failed:',
|
|
927
|
+
console.error('[daemon] Connector health tick failed:', errorMessage(err))
|
|
927
928
|
})
|
|
928
929
|
}
|
|
929
930
|
|
|
930
|
-
tick()
|
|
931
|
+
if (options?.runImmediately !== false) tick()
|
|
931
932
|
ds.connectorHealthIntervalId = setInterval(tick, CONNECTOR_HEALTH_CHECK_INTERVAL)
|
|
932
933
|
}
|
|
933
934
|
|
|
@@ -949,7 +950,7 @@ function runConsolidationTick() {
|
|
|
949
950
|
}
|
|
950
951
|
}),
|
|
951
952
|
).catch((err: unknown) => {
|
|
952
|
-
console.error('[daemon] Memory consolidation failed:',
|
|
953
|
+
console.error('[daemon] Memory consolidation failed:', errorMessage(err))
|
|
953
954
|
})
|
|
954
955
|
}
|
|
955
956
|
|
|
@@ -978,14 +979,6 @@ function stopMemoryConsolidation() {
|
|
|
978
979
|
|
|
979
980
|
const EVAL_DEFAULT_INTERVAL_MS = 24 * 3600_000 // 24 hours
|
|
980
981
|
|
|
981
|
-
function parseCronToMs(cron: string | null | undefined): number | null {
|
|
982
|
-
if (!cron || typeof cron !== 'string') return null
|
|
983
|
-
// Simple heuristic: extract hours from common cron patterns like "0 */6 * * *"
|
|
984
|
-
const hourMatch = cron.match(/\*\/(\d+)/)
|
|
985
|
-
if (hourMatch) return parseInt(hourMatch[1], 10) * 3600_000
|
|
986
|
-
return EVAL_DEFAULT_INTERVAL_MS
|
|
987
|
-
}
|
|
988
|
-
|
|
989
982
|
async function runEvalSchedulerTick() {
|
|
990
983
|
try {
|
|
991
984
|
const settings = loadSettings()
|
|
@@ -1009,11 +1002,11 @@ async function runEvalSchedulerTick() {
|
|
|
1009
1002
|
type: result.percentage >= 60 ? 'info' : 'warning',
|
|
1010
1003
|
})
|
|
1011
1004
|
} catch (err: unknown) {
|
|
1012
|
-
console.error(`[daemon:eval] Failed for agent ${agentId}:`,
|
|
1005
|
+
console.error(`[daemon:eval] Failed for agent ${agentId}:`, errorMessage(err))
|
|
1013
1006
|
}
|
|
1014
1007
|
}
|
|
1015
1008
|
} catch (err: unknown) {
|
|
1016
|
-
console.error('[daemon:eval] Scheduler tick error:',
|
|
1009
|
+
console.error('[daemon:eval] Scheduler tick error:', errorMessage(err))
|
|
1017
1010
|
}
|
|
1018
1011
|
}
|
|
1019
1012
|
|
|
@@ -1022,7 +1015,7 @@ function startEvalScheduler() {
|
|
|
1022
1015
|
try {
|
|
1023
1016
|
const settings = loadSettings()
|
|
1024
1017
|
if (!settings.autonomyEvalEnabled) return
|
|
1025
|
-
const intervalMs = parseCronToMs(settings.autonomyEvalCron) || EVAL_DEFAULT_INTERVAL_MS
|
|
1018
|
+
const intervalMs = parseCronToMs(settings.autonomyEvalCron, EVAL_DEFAULT_INTERVAL_MS) || EVAL_DEFAULT_INTERVAL_MS
|
|
1026
1019
|
ds.evalSchedulerIntervalId = setInterval(runEvalSchedulerTick, intervalMs)
|
|
1027
1020
|
console.log(`[daemon:eval] Eval scheduler started (interval=${Math.round(intervalMs / 3600_000)}h)`)
|
|
1028
1021
|
} catch {
|
|
@@ -1086,6 +1079,10 @@ export async function runDaemonHealthCheckNow() {
|
|
|
1086
1079
|
])
|
|
1087
1080
|
}
|
|
1088
1081
|
|
|
1082
|
+
export async function runConnectorHealthCheckNowForTest(now = Date.now()) {
|
|
1083
|
+
await runConnectorHealthChecks(now)
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1089
1086
|
export function getDaemonStatus() {
|
|
1090
1087
|
const queue = loadQueue()
|
|
1091
1088
|
const schedules = loadSchedules()
|
|
@@ -28,7 +28,7 @@ describe('data-dir resolution', () => {
|
|
|
28
28
|
|
|
29
29
|
try {
|
|
30
30
|
const result = spawnSync(process.execPath, ['--import', 'tsx', '--input-type=module', '--eval', `
|
|
31
|
-
const modNs = await import('./src/lib/server/data-dir
|
|
31
|
+
const modNs = await import('./src/lib/server/data-dir')
|
|
32
32
|
const mod = modNs.default || modNs['module.exports'] || modNs
|
|
33
33
|
console.log(JSON.stringify({
|
|
34
34
|
dataDir: mod.DATA_DIR,
|
|
@@ -60,12 +60,12 @@ describe('data-dir resolution', () => {
|
|
|
60
60
|
|
|
61
61
|
try {
|
|
62
62
|
const env = { ...process.env, HOME: fakeHome, npm_lifecycle_event: 'build:ci' }
|
|
63
|
-
delete env.DATA_DIR
|
|
64
|
-
delete env.WORKSPACE_DIR
|
|
65
|
-
delete env.BROWSER_PROFILES_DIR
|
|
63
|
+
delete (env as any).DATA_DIR
|
|
64
|
+
delete (env as any).WORKSPACE_DIR
|
|
65
|
+
delete (env as any).BROWSER_PROFILES_DIR
|
|
66
66
|
|
|
67
67
|
const result = spawnSync(process.execPath, ['--import', 'tsx', '--input-type=module', '--eval', `
|
|
68
|
-
const modNs = await import('./src/lib/server/data-dir
|
|
68
|
+
const modNs = await import('./src/lib/server/data-dir')
|
|
69
69
|
const mod = modNs.default || modNs['module.exports'] || modNs
|
|
70
70
|
console.log(JSON.stringify({
|
|
71
71
|
isBuildBootstrap: mod.IS_BUILD_BOOTSTRAP,
|
|
@@ -20,6 +20,10 @@ function resolveDataDir(): string {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export const DATA_DIR = resolveDataDir()
|
|
23
|
+
export const CONNECTORS_DATA_DIR = path.join(DATA_DIR, 'connectors')
|
|
24
|
+
export const OPENCLAW_DATA_DIR = path.join(DATA_DIR, 'openclaw')
|
|
25
|
+
export const MEMORY_IMAGES_DIR = path.join(DATA_DIR, 'memory-images')
|
|
26
|
+
export const APP_LOG_PATH = path.join(DATA_DIR, 'app.log')
|
|
23
27
|
|
|
24
28
|
function supportsChildWrites(dir: string): boolean {
|
|
25
29
|
try {
|
|
@@ -275,7 +275,7 @@ describe('delegation-jobs-advanced', () => {
|
|
|
275
275
|
// Kept: file-6..file-9 (4) + all batch2 (10) + all batch3 (10) = 24
|
|
276
276
|
const first = afterBatch3.artifacts[0]
|
|
277
277
|
assert.equal(first.type, 'file')
|
|
278
|
-
assert.equal(first.value, '/output/file-6
|
|
278
|
+
assert.equal(first.value, '/output/file-6')
|
|
279
279
|
|
|
280
280
|
const last = afterBatch3.artifacts[23]
|
|
281
281
|
assert.equal(last.type, 'image')
|
|
@@ -99,6 +99,93 @@ describe('delegation-jobs', () => {
|
|
|
99
99
|
assert.match(String(latest?.error || ''), /interrupted/i)
|
|
100
100
|
})
|
|
101
101
|
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Reliability fix #4: atomic updateDelegationJob preserves fields
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
it('atomic update preserves all original fields', () => {
|
|
107
|
+
const job = delegationJobs.createDelegationJob({
|
|
108
|
+
kind: 'subagent',
|
|
109
|
+
task: 'Atomic update test',
|
|
110
|
+
agentId: 'ag-atomic',
|
|
111
|
+
agentName: 'Atomic Agent',
|
|
112
|
+
parentSessionId: 'parent-atomic',
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
// Partial update should not lose unrelated fields
|
|
116
|
+
const updated = delegationJobs.updateDelegationJob(job.id, { status: 'running' })
|
|
117
|
+
assert.ok(updated)
|
|
118
|
+
assert.equal(updated!.status, 'running')
|
|
119
|
+
assert.equal(updated!.task, 'Atomic update test')
|
|
120
|
+
assert.equal(updated!.agentId, 'ag-atomic')
|
|
121
|
+
assert.equal(updated!.agentName, 'Atomic Agent')
|
|
122
|
+
assert.equal(updated!.parentSessionId, 'parent-atomic')
|
|
123
|
+
assert.equal(updated!.kind, 'subagent')
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('sequential updates preserve intermediate state', () => {
|
|
127
|
+
const job = delegationJobs.createDelegationJob({
|
|
128
|
+
kind: 'subagent',
|
|
129
|
+
task: 'Sequential updates',
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
delegationJobs.updateDelegationJob(job.id, { status: 'running', startedAt: Date.now() })
|
|
133
|
+
delegationJobs.updateDelegationJob(job.id, { result: 'partial result' })
|
|
134
|
+
|
|
135
|
+
const final = delegationJobs.getDelegationJob(job.id)
|
|
136
|
+
assert.ok(final)
|
|
137
|
+
assert.equal(final!.status, 'running')
|
|
138
|
+
assert.equal(final!.result, 'partial result')
|
|
139
|
+
assert.ok(final!.startedAt! > 0)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('updateDelegationJob returns null for non-existent job', () => {
|
|
143
|
+
const result = delegationJobs.updateDelegationJob('nonexistent-abc', { status: 'running' })
|
|
144
|
+
assert.equal(result, null)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// Reliability fix (bonus): cancel ordering — state committed before handle delete
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
it('cancel records checkpoint and timestamp atomically', () => {
|
|
152
|
+
const job = delegationJobs.createDelegationJob({
|
|
153
|
+
kind: 'subagent',
|
|
154
|
+
task: 'Cancel ordering test',
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const cancelled = delegationJobs.cancelDelegationJob(job.id)
|
|
158
|
+
assert.ok(cancelled)
|
|
159
|
+
assert.equal(cancelled!.status, 'cancelled')
|
|
160
|
+
assert.ok(cancelled!.completedAt! > 0)
|
|
161
|
+
const cps = cancelled!.checkpoints ?? []
|
|
162
|
+
const lastCp = cps[cps.length - 1]
|
|
163
|
+
assert.equal(lastCp?.status, 'cancelled')
|
|
164
|
+
assert.equal(lastCp?.note, 'Job cancelled')
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('cancel is idempotent — repeated cancel returns unchanged job', () => {
|
|
168
|
+
const job = delegationJobs.createDelegationJob({
|
|
169
|
+
kind: 'subagent',
|
|
170
|
+
task: 'Idempotent cancel',
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
const first = delegationJobs.cancelDelegationJob(job.id)!
|
|
174
|
+
const second = delegationJobs.cancelDelegationJob(job.id)!
|
|
175
|
+
assert.equal(second.status, 'cancelled')
|
|
176
|
+
assert.equal((second.checkpoints ?? []).length, (first.checkpoints ?? []).length)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('does not cancel completed jobs', () => {
|
|
180
|
+
const job = delegationJobs.createDelegationJob({
|
|
181
|
+
kind: 'subagent',
|
|
182
|
+
task: 'Completed job cancel',
|
|
183
|
+
})
|
|
184
|
+
delegationJobs.completeDelegationJob(job.id, 'All done')
|
|
185
|
+
const afterCancel = delegationJobs.cancelDelegationJob(job.id)
|
|
186
|
+
assert.equal(afterCancel!.status, 'completed')
|
|
187
|
+
})
|
|
188
|
+
|
|
102
189
|
it('cancels all running jobs for a parent session', () => {
|
|
103
190
|
const jobA = delegationJobs.createDelegationJob({
|
|
104
191
|
kind: 'delegate',
|