@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
|
@@ -4,6 +4,7 @@ import { useEffect, useRef, useCallback, useState, useMemo } from 'react'
|
|
|
4
4
|
import { useChatroomStore } from '@/stores/use-chatroom-store'
|
|
5
5
|
import type { StreamingAgent } from '@/stores/use-chatroom-store'
|
|
6
6
|
import { useAppStore } from '@/stores/use-app-store'
|
|
7
|
+
import { useNow } from '@/hooks/use-now'
|
|
7
8
|
import { useWs } from '@/hooks/use-ws'
|
|
8
9
|
import { ChatroomMessageBubble } from './chatroom-message'
|
|
9
10
|
import { ChatroomInput } from './chatroom-input'
|
|
@@ -35,10 +36,10 @@ function getMemberRole(chatroom: Chatroom, agentId: string): string {
|
|
|
35
36
|
return member?.role || 'member'
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
function isAgentMuted(chatroom: Chatroom, agentId: string): boolean {
|
|
39
|
+
function isAgentMuted(chatroom: Chatroom, agentId: string, now: number | null): boolean {
|
|
39
40
|
const member = getMemberFromChatroom(chatroom, agentId)
|
|
40
41
|
if (!member?.mutedUntil) return false
|
|
41
|
-
return new Date(member.mutedUntil).getTime() >
|
|
42
|
+
return !!now && new Date(member.mutedUntil).getTime() > now
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
type MomentType = { kind: 'heartbeat' } | { kind: 'tool'; name: string; input: string }
|
|
@@ -69,10 +70,11 @@ function AgentHeartbeatListeners({ agentIds, onPulse }: { agentIds: string[]; on
|
|
|
69
70
|
|
|
70
71
|
const GROUP_THRESHOLD_MS = 2 * 60 * 1000
|
|
71
72
|
|
|
72
|
-
function dayLabel(ts: number): string {
|
|
73
|
+
function dayLabel(ts: number, now: number | null): string {
|
|
73
74
|
const d = new Date(ts)
|
|
74
|
-
|
|
75
|
-
const
|
|
75
|
+
if (!now) return d.toISOString().slice(0, 10)
|
|
76
|
+
const nowDate = new Date(now)
|
|
77
|
+
const today = new Date(nowDate.getFullYear(), nowDate.getMonth(), nowDate.getDate())
|
|
76
78
|
const msgDay = new Date(d.getFullYear(), d.getMonth(), d.getDate())
|
|
77
79
|
const diff = today.getTime() - msgDay.getTime()
|
|
78
80
|
if (diff === 0) return 'Today'
|
|
@@ -81,6 +83,7 @@ function dayLabel(ts: number): string {
|
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
export function ChatroomView() {
|
|
86
|
+
const now = useNow()
|
|
84
87
|
const currentChatroomId = useChatroomStore((s) => s.currentChatroomId)
|
|
85
88
|
const chatrooms = useChatroomStore((s) => s.chatrooms)
|
|
86
89
|
const streamingAgents = useChatroomStore((s) => s.streamingAgents)
|
|
@@ -165,7 +168,7 @@ export function ChatroomView() {
|
|
|
165
168
|
: []
|
|
166
169
|
), [chatroom, pinnedIds])
|
|
167
170
|
const memberAgentIds = chatroom?.agentIds || []
|
|
168
|
-
const mutedCount = chatroom ? chatroom.agentIds.filter((agentId) => isAgentMuted(chatroom, agentId)).length : 0
|
|
171
|
+
const mutedCount = chatroom ? chatroom.agentIds.filter((agentId) => isAgentMuted(chatroom, agentId, now)).length : 0
|
|
169
172
|
const adminCount = chatroom ? chatroom.agentIds.filter((agentId) => getMemberRole(chatroom, agentId) === 'admin').length : 0
|
|
170
173
|
const lastReadAt = chatroom ? (lastReadTimestamps[chatroom.id] || 0) : 0
|
|
171
174
|
const unreadCount = useMemo(() => (
|
|
@@ -277,7 +280,7 @@ export function ChatroomView() {
|
|
|
277
280
|
{memberAgents.slice(0, 5).map((agent) => {
|
|
278
281
|
const role = getMemberRole(chatroom, agent.id)
|
|
279
282
|
const badge = getRoleBadge(role)
|
|
280
|
-
const muted = isAgentMuted(chatroom, agent.id)
|
|
283
|
+
const muted = isAgentMuted(chatroom, agent.id, Date.now())
|
|
281
284
|
return (
|
|
282
285
|
<Tooltip key={agent.id}>
|
|
283
286
|
<TooltipTrigger asChild>
|
|
@@ -411,7 +414,7 @@ export function ChatroomView() {
|
|
|
411
414
|
{showDaySep && (
|
|
412
415
|
<div className="flex items-center gap-3 px-4 py-3">
|
|
413
416
|
<div className="flex-1 h-px bg-white/[0.06]" />
|
|
414
|
-
<span className="text-[10px] font-600 text-text-3 uppercase tracking-wider">{dayLabel(msg.time)}</span>
|
|
417
|
+
<span className="text-[10px] font-600 text-text-3 uppercase tracking-wider">{dayLabel(msg.time, now)}</span>
|
|
415
418
|
<div className="flex-1 h-px bg-white/[0.06]" />
|
|
416
419
|
</div>
|
|
417
420
|
)}
|
|
@@ -540,7 +543,7 @@ function RoomDetailsPanel({
|
|
|
540
543
|
<div className="space-y-2">
|
|
541
544
|
{memberAgents.map((agent) => {
|
|
542
545
|
const role = getMemberRole(chatroom, agent.id)
|
|
543
|
-
const muted = isAgentMuted(chatroom, agent.id)
|
|
546
|
+
const muted = isAgentMuted(chatroom, agent.id, Date.now())
|
|
544
547
|
return (
|
|
545
548
|
<button
|
|
546
549
|
key={agent.id}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from 'react'
|
|
4
4
|
import { api } from '@/lib/api-client'
|
|
5
|
+
import { useNow } from '@/hooks/use-now'
|
|
5
6
|
import type { ConnectorHealthEvent, ConnectorHealthEventType } from '@/types'
|
|
6
7
|
|
|
7
8
|
interface HealthResponse {
|
|
@@ -17,10 +18,10 @@ const EVENT_CONFIG: Record<ConnectorHealthEventType, { color: string; label: str
|
|
|
17
18
|
disconnected: { color: 'bg-amber-400', label: 'Disconnected' },
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
function formatTimestamp(ts: string): string {
|
|
21
|
+
function formatTimestamp(ts: string, now: number | null): string {
|
|
21
22
|
const d = new Date(ts)
|
|
22
|
-
|
|
23
|
-
const diffMs = now
|
|
23
|
+
if (!now) return 'recently'
|
|
24
|
+
const diffMs = now - d.getTime()
|
|
24
25
|
const diffMin = Math.floor(diffMs / 60_000)
|
|
25
26
|
const diffHr = Math.floor(diffMs / 3_600_000)
|
|
26
27
|
const diffDay = Math.floor(diffMs / 86_400_000)
|
|
@@ -39,6 +40,7 @@ function uptimeBadgeColor(pct: number): string {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
export function ConnectorHealth({ connectorId }: { connectorId: string }) {
|
|
43
|
+
const now = useNow()
|
|
42
44
|
const [data, setData] = useState<HealthResponse | null>(null)
|
|
43
45
|
const [loading, setLoading] = useState(true)
|
|
44
46
|
|
|
@@ -104,7 +106,7 @@ export function ConnectorHealth({ connectorId }: { connectorId: string }) {
|
|
|
104
106
|
<div className="min-w-0 flex-1">
|
|
105
107
|
<div className="flex items-center gap-2">
|
|
106
108
|
<span className="text-[13px] font-600 text-text-2">{cfg.label}</span>
|
|
107
|
-
<span className="text-[11px] text-text-3">{formatTimestamp(ev.timestamp)}</span>
|
|
109
|
+
<span className="text-[11px] text-text-3">{formatTimestamp(ev.timestamp, now)}</span>
|
|
108
110
|
</div>
|
|
109
111
|
{ev.message && (
|
|
110
112
|
<p className="text-[12px] text-text-3/70 mt-0.5 leading-[1.4] break-words">{ev.message}</p>
|
|
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { useChatroomStore } from '@/stores/use-chatroom-store'
|
|
6
6
|
import { useWs } from '@/hooks/use-ws'
|
|
7
|
+
import { useMountedRef } from '@/hooks/use-mounted-ref'
|
|
7
8
|
import { api } from '@/lib/api-client'
|
|
8
9
|
import type { Connector } from '@/types'
|
|
9
10
|
import {
|
|
@@ -55,6 +56,7 @@ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
55
56
|
const [loaded, setLoaded] = useState(false)
|
|
56
57
|
const [error, setError] = useState<string | null>(null)
|
|
57
58
|
const [groupFilter, setGroupFilter] = useState<'all' | ConnectorGroup>('all')
|
|
59
|
+
const mountedRef = useMountedRef()
|
|
58
60
|
const openConnector = useCallback((id: string | null) => {
|
|
59
61
|
setEditingConnectorId(id)
|
|
60
62
|
setConnectorSheetOpen(true)
|
|
@@ -62,9 +64,8 @@ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
62
64
|
|
|
63
65
|
const refresh = useCallback(async () => {
|
|
64
66
|
await Promise.all([loadConnectors(), loadAgents(), loadChatrooms()])
|
|
65
|
-
setLoaded(true)
|
|
66
|
-
|
|
67
|
-
}, [loadConnectors, loadAgents])
|
|
67
|
+
if (mountedRef.current) setLoaded(true)
|
|
68
|
+
}, [loadConnectors, loadAgents, loadChatrooms, mountedRef])
|
|
68
69
|
|
|
69
70
|
useEffect(() => { void refresh() }, [refresh])
|
|
70
71
|
useWs('connectors', loadConnectors, 15_000)
|
|
@@ -77,34 +78,38 @@ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
77
78
|
const handleToggle = async (e: React.MouseEvent, c: Connector) => {
|
|
78
79
|
e.stopPropagation()
|
|
79
80
|
const action = c.status === 'running' ? 'stop' : 'start'
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
if (mountedRef.current) {
|
|
82
|
+
setToggling(c.id)
|
|
83
|
+
setError(null)
|
|
84
|
+
}
|
|
82
85
|
try {
|
|
83
86
|
await api('PUT', `/connectors/${c.id}`, { action })
|
|
84
87
|
await refresh()
|
|
85
88
|
} catch (err: unknown) {
|
|
86
89
|
const msg = err instanceof Error && err.message ? err.message : `Failed to ${action}`
|
|
87
|
-
setError(msg)
|
|
90
|
+
if (mountedRef.current) setError(msg)
|
|
88
91
|
await refresh()
|
|
89
92
|
} finally {
|
|
90
|
-
setToggling(null)
|
|
93
|
+
if (mountedRef.current) setToggling(null)
|
|
91
94
|
}
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
const handleReconnect = async (e: React.MouseEvent, c: Connector) => {
|
|
95
98
|
e.stopPropagation()
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
if (mountedRef.current) {
|
|
100
|
+
setReconnecting(c.id)
|
|
101
|
+
setError(null)
|
|
102
|
+
}
|
|
98
103
|
try {
|
|
99
104
|
try { await api('PUT', `/connectors/${c.id}`, { action: 'stop' }) } catch { /* may already be stopped */ }
|
|
100
105
|
await api('PUT', `/connectors/${c.id}`, { action: 'start' })
|
|
101
106
|
await refresh()
|
|
102
107
|
} catch (err: unknown) {
|
|
103
108
|
const msg = err instanceof Error && err.message ? err.message : 'Failed to reconnect'
|
|
104
|
-
setError(msg)
|
|
109
|
+
if (mountedRef.current) setError(msg)
|
|
105
110
|
await refresh()
|
|
106
111
|
} finally {
|
|
107
|
-
setReconnecting(null)
|
|
112
|
+
if (mountedRef.current) setReconnecting(null)
|
|
108
113
|
}
|
|
109
114
|
}
|
|
110
115
|
|
|
@@ -16,6 +16,7 @@ import { HintTip } from '@/components/shared/hint-tip'
|
|
|
16
16
|
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
17
17
|
import { useChatroomStore } from '@/stores/use-chatroom-store'
|
|
18
18
|
import { ConnectorHealth } from '@/components/connectors/connector-health'
|
|
19
|
+
import { errorMessage } from '@/lib/shared-utils'
|
|
19
20
|
|
|
20
21
|
/** Auto-detect URLs in text and make them clickable links that open in a new tab */
|
|
21
22
|
function linkify(text: string) {
|
|
@@ -489,6 +490,7 @@ export function ConnectorSheet() {
|
|
|
489
490
|
const connectors = useAppStore((s) => s.connectors)
|
|
490
491
|
const loadConnectors = useAppStore((s) => s.loadConnectors)
|
|
491
492
|
const agents = useAppStore((s) => s.agents)
|
|
493
|
+
const appSettings = useAppStore((s) => s.appSettings)
|
|
492
494
|
const credentials = useAppStore((s) => s.credentials)
|
|
493
495
|
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
494
496
|
const loadCredentials = useAppStore((s) => s.loadCredentials)
|
|
@@ -520,6 +522,10 @@ export function ConnectorSheet() {
|
|
|
520
522
|
const [confirmDelete, setConfirmDelete] = useState(false)
|
|
521
523
|
const [confirmWhatsAppAction, setConfirmWhatsAppAction] = useState<'unlink' | 'repair' | null>(null)
|
|
522
524
|
const [deleting, setDeleting] = useState(false)
|
|
525
|
+
const localAllowlistCount = config.allowFrom ? config.allowFrom.split(',').map((entry) => entry.trim()).filter(Boolean).length : 0
|
|
526
|
+
const globalWhatsAppAllowlistCount = platform === 'whatsapp' && Array.isArray(appSettings.whatsappApprovedContacts)
|
|
527
|
+
? appSettings.whatsappApprovedContacts.length
|
|
528
|
+
: 0
|
|
523
529
|
|
|
524
530
|
const editing = editingId ? connectors[editingId] as Connector | undefined : null
|
|
525
531
|
|
|
@@ -640,7 +646,7 @@ export function ConnectorSheet() {
|
|
|
640
646
|
setOpen(false)
|
|
641
647
|
setEditingId(null)
|
|
642
648
|
} catch (err: unknown) {
|
|
643
|
-
toast.error(
|
|
649
|
+
toast.error(errorMessage(err))
|
|
644
650
|
} finally {
|
|
645
651
|
setSaving(false)
|
|
646
652
|
}
|
|
@@ -665,7 +671,7 @@ export function ConnectorSheet() {
|
|
|
665
671
|
await loadConnectors()
|
|
666
672
|
} catch (err: unknown) {
|
|
667
673
|
setWaConnecting(false)
|
|
668
|
-
toast.error(`Failed to ${action}: ${
|
|
674
|
+
toast.error(`Failed to ${action}: ${errorMessage(err)}`)
|
|
669
675
|
} finally {
|
|
670
676
|
setActionLoading(false)
|
|
671
677
|
}
|
|
@@ -681,7 +687,7 @@ export function ConnectorSheet() {
|
|
|
681
687
|
setOpen(false)
|
|
682
688
|
setEditingId(null)
|
|
683
689
|
} catch (err: unknown) {
|
|
684
|
-
toast.error(`Failed to delete connector: ${
|
|
690
|
+
toast.error(`Failed to delete connector: ${errorMessage(err)}`)
|
|
685
691
|
} finally {
|
|
686
692
|
setDeleting(false)
|
|
687
693
|
}
|
|
@@ -699,7 +705,7 @@ export function ConnectorSheet() {
|
|
|
699
705
|
setConfirmWhatsAppAction(null)
|
|
700
706
|
await loadConnectors()
|
|
701
707
|
} catch (err: unknown) {
|
|
702
|
-
toast.error(`Failed to ${mode === 'unlink' ? 'unlink' : 're-pair'}: ${
|
|
708
|
+
toast.error(`Failed to ${mode === 'unlink' ? 'unlink' : 're-pair'}: ${errorMessage(err)}`)
|
|
703
709
|
} finally {
|
|
704
710
|
setActionLoading(false)
|
|
705
711
|
}
|
|
@@ -1051,7 +1057,7 @@ export function ConnectorSheet() {
|
|
|
1051
1057
|
setNewCredName('')
|
|
1052
1058
|
setNewCredValue('')
|
|
1053
1059
|
} catch (err: unknown) {
|
|
1054
|
-
toast.error(`Failed to save: ${
|
|
1060
|
+
toast.error(`Failed to save: ${errorMessage(err)}`)
|
|
1055
1061
|
} finally {
|
|
1056
1062
|
setSavingCred(false)
|
|
1057
1063
|
}
|
|
@@ -1158,7 +1164,7 @@ export function ConnectorSheet() {
|
|
|
1158
1164
|
· Debounce: <span className="text-text-2">{doctorPolicy.inboundDebounceMs ?? 700}ms</span>
|
|
1159
1165
|
</div>
|
|
1160
1166
|
<div className="rounded-[10px] border border-white/[0.06] bg-white/[0.02] px-3 py-2 text-[12px] text-text-3/80">
|
|
1161
|
-
Allowlist: <span className="text-text-2">{
|
|
1167
|
+
Allowlist: <span className="text-text-2">{localAllowlistCount + globalWhatsAppAllowlistCount}</span>{' '}
|
|
1162
1168
|
· Reactions: <span className="text-text-2">{doctorPolicy.statusReactions === false ? 'off' : 'on'}</span>{' '}
|
|
1163
1169
|
· Typing: <span className="text-text-2">{doctorPolicy.typingIndicators === false ? 'off' : 'on'}</span>
|
|
1164
1170
|
</div>
|
|
@@ -5,6 +5,8 @@ import { AreaChart, Area, ResponsiveContainer, Tooltip } from 'recharts'
|
|
|
5
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
6
|
import { useChatStore } from '@/stores/use-chat-store'
|
|
7
7
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
8
|
+
import { useMountedRef } from '@/hooks/use-mounted-ref'
|
|
9
|
+
import { useNow } from '@/hooks/use-now'
|
|
8
10
|
import { api } from '@/lib/api-client'
|
|
9
11
|
import { isLocalhostBrowser, isVisibleSessionForViewer } from '@/lib/local-observability'
|
|
10
12
|
import { getSessionLastMessage } from '@/lib/session-summary'
|
|
@@ -12,8 +14,9 @@ import { getNotificationActivityAt, getNotificationOccurrenceCount } from '@/lib
|
|
|
12
14
|
import type { Agent, Session, ActivityEntry, BoardTask, AppNotification } from '@/types'
|
|
13
15
|
import { HintTip } from '@/components/shared/hint-tip'
|
|
14
16
|
|
|
15
|
-
function timeAgo(ts: number): string {
|
|
16
|
-
|
|
17
|
+
function timeAgo(ts: number, now: number | null): string {
|
|
18
|
+
if (!now) return 'recently'
|
|
19
|
+
const diff = now - ts
|
|
17
20
|
const mins = Math.floor(diff / 60000)
|
|
18
21
|
if (mins < 1) return 'just now'
|
|
19
22
|
if (mins < 60) return `${mins}m ago`
|
|
@@ -23,8 +26,9 @@ function timeAgo(ts: number): string {
|
|
|
23
26
|
return `${days}d ago`
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
function timeUntil(ts: number): string {
|
|
27
|
-
|
|
29
|
+
function timeUntil(ts: number, now: number | null): string {
|
|
30
|
+
if (!now) return 'soon'
|
|
31
|
+
const diff = ts - now
|
|
28
32
|
if (diff <= 0) return 'now'
|
|
29
33
|
const mins = Math.floor(diff / 60000)
|
|
30
34
|
if (mins < 60) return `in ${mins}m`
|
|
@@ -69,6 +73,7 @@ const PLATFORM_LABELS: Record<string, string> = {
|
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
export function HomeView() {
|
|
76
|
+
const now = useNow()
|
|
72
77
|
const agents = useAppStore((s) => s.agents)
|
|
73
78
|
const sessions = useAppStore((s) => s.sessions)
|
|
74
79
|
const currentUser = useAppStore((s) => s.currentUser)
|
|
@@ -89,9 +94,14 @@ export function HomeView() {
|
|
|
89
94
|
const setCurrentSession = useAppStore((s) => s.setCurrentSession)
|
|
90
95
|
const setEditingTaskId = useAppStore((s) => s.setEditingTaskId)
|
|
91
96
|
const setTaskSheetOpen = useAppStore((s) => s.setTaskSheetOpen)
|
|
92
|
-
const setMessages = useChatStore((s) => s.setMessages)
|
|
93
97
|
const [todayCost, setTodayCost] = useState(0)
|
|
94
98
|
const [costTrend, setCostTrend] = useState<{ cost: number; bucket: string }[]>([])
|
|
99
|
+
const [localhostBrowser, setLocalhostBrowser] = useState(false)
|
|
100
|
+
const mountedRef = useMountedRef()
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
setLocalhostBrowser(isLocalhostBrowser())
|
|
104
|
+
}, [])
|
|
95
105
|
|
|
96
106
|
const allAgents = Object.values(agents).filter((a) => !a.trashedAt)
|
|
97
107
|
const pinnedAgents = allAgents.filter((a) => a.pinned)
|
|
@@ -99,10 +109,10 @@ export function HomeView() {
|
|
|
99
109
|
const recentChats = useMemo(
|
|
100
110
|
() =>
|
|
101
111
|
Object.values(sessions)
|
|
102
|
-
.filter((session) => isVisibleSessionForViewer(session, currentUser, { localhost:
|
|
112
|
+
.filter((session) => isVisibleSessionForViewer(session, currentUser, { localhost: localhostBrowser }))
|
|
103
113
|
.sort((a, b) => (b.lastActiveAt || 0) - (a.lastActiveAt || 0))
|
|
104
114
|
.slice(0, 5),
|
|
105
|
-
[currentUser, sessions],
|
|
115
|
+
[currentUser, localhostBrowser, sessions],
|
|
106
116
|
)
|
|
107
117
|
|
|
108
118
|
// Quick stats
|
|
@@ -130,12 +140,12 @@ export function HomeView() {
|
|
|
130
140
|
|
|
131
141
|
// Upcoming schedules
|
|
132
142
|
const upcomingSchedules = useMemo(() => {
|
|
133
|
-
const
|
|
143
|
+
const currentNow = now ?? 0
|
|
134
144
|
return Object.values(schedules)
|
|
135
|
-
.filter((s) => s.status === 'active' && s.nextRunAt && s.nextRunAt >
|
|
145
|
+
.filter((s) => s.status === 'active' && s.nextRunAt && s.nextRunAt > currentNow)
|
|
136
146
|
.sort((a, b) => (a.nextRunAt || 0) - (b.nextRunAt || 0))
|
|
137
147
|
.slice(0, 5)
|
|
138
|
-
}, [schedules])
|
|
148
|
+
}, [now, schedules])
|
|
139
149
|
|
|
140
150
|
// Unread notifications
|
|
141
151
|
const unreadNotifications = useMemo(
|
|
@@ -148,14 +158,16 @@ export function HomeView() {
|
|
|
148
158
|
|
|
149
159
|
// Load data on mount
|
|
150
160
|
useEffect(() => {
|
|
161
|
+
let cancelled = false
|
|
151
162
|
void loadActivity({ limit: 8 })
|
|
152
163
|
void loadSchedules()
|
|
153
164
|
void loadNotifications()
|
|
154
165
|
const connectorTimer = window.setTimeout(() => {
|
|
155
|
-
void loadConnectors()
|
|
166
|
+
if (!cancelled) void loadConnectors()
|
|
156
167
|
}, 1200)
|
|
157
168
|
api<{ records: Array<{ estimatedCost: number }>; timeSeries: Array<{ cost: number; bucket: string }> }>('GET', '/usage?range=7d')
|
|
158
169
|
.then((data) => {
|
|
170
|
+
if (cancelled || !mountedRef.current) return
|
|
159
171
|
const series = (data.timeSeries || []).map((pt: { cost: number; bucket?: string }) => ({ cost: pt.cost, bucket: pt.bucket || '' }))
|
|
160
172
|
setCostTrend(series)
|
|
161
173
|
const todayBucket = new Date().toISOString().slice(0, 10)
|
|
@@ -163,12 +175,14 @@ export function HomeView() {
|
|
|
163
175
|
setTodayCost(todayPt?.cost || 0)
|
|
164
176
|
})
|
|
165
177
|
.catch(() => {})
|
|
166
|
-
return () =>
|
|
178
|
+
return () => {
|
|
179
|
+
cancelled = true
|
|
180
|
+
window.clearTimeout(connectorTimer)
|
|
181
|
+
}
|
|
167
182
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
168
|
-
}, [])
|
|
183
|
+
}, [mountedRef])
|
|
169
184
|
|
|
170
185
|
const handleAgentClick = (agent: Agent) => {
|
|
171
|
-
setMessages([])
|
|
172
186
|
void setCurrentAgent(agent.id)
|
|
173
187
|
setActiveView('agents')
|
|
174
188
|
}
|
|
@@ -225,7 +239,7 @@ export function HomeView() {
|
|
|
225
239
|
</div>
|
|
226
240
|
|
|
227
241
|
{(() => {
|
|
228
|
-
const
|
|
242
|
+
const currentNow = now ?? 0
|
|
229
243
|
const items = [
|
|
230
244
|
...allTasks
|
|
231
245
|
.filter((task) => task.pendingApproval)
|
|
@@ -244,7 +258,7 @@ export function HomeView() {
|
|
|
244
258
|
id: `failed:${task.id}`,
|
|
245
259
|
tone: 'danger' as const,
|
|
246
260
|
label: task.title,
|
|
247
|
-
meta: `Failed ${timeAgo(task.updatedAt || task.createdAt)}`,
|
|
261
|
+
meta: `Failed ${timeAgo(task.updatedAt || task.createdAt, now)}`,
|
|
248
262
|
onClick: () => handleTaskClick(task),
|
|
249
263
|
})),
|
|
250
264
|
...allConnectors
|
|
@@ -258,7 +272,7 @@ export function HomeView() {
|
|
|
258
272
|
onClick: () => setActiveView('connectors'),
|
|
259
273
|
})),
|
|
260
274
|
...Object.values(schedules)
|
|
261
|
-
.filter((schedule) => schedule.status === 'active' && schedule.nextRunAt && schedule.nextRunAt <
|
|
275
|
+
.filter((schedule) => schedule.status === 'active' && schedule.nextRunAt && schedule.nextRunAt < currentNow)
|
|
262
276
|
.slice(0, 2)
|
|
263
277
|
.map((schedule) => ({
|
|
264
278
|
id: `schedule:${schedule.id}`,
|
|
@@ -396,7 +410,7 @@ export function HomeView() {
|
|
|
396
410
|
x{getNotificationOccurrenceCount(n)}
|
|
397
411
|
</span>
|
|
398
412
|
)}
|
|
399
|
-
<span className="text-[10px] text-text-3/40">{timeAgo(getNotificationActivityAt(n))}</span>
|
|
413
|
+
<span className="text-[10px] text-text-3/40">{timeAgo(getNotificationActivityAt(n), now)}</span>
|
|
400
414
|
</div>
|
|
401
415
|
</button>
|
|
402
416
|
))}
|
|
@@ -452,7 +466,7 @@ export function HomeView() {
|
|
|
452
466
|
<div className="flex-1 min-w-0">
|
|
453
467
|
<span className="text-[13px] font-500 text-text truncate block">{task.title}</span>
|
|
454
468
|
<span className="text-[11px] text-text-3/50">
|
|
455
|
-
{agent?.name || 'Unassigned'} · {task.status === 'running' ? 'running' : 'queued'}{task.startedAt ? ` · ${timeAgo(task.startedAt)}` : ''}
|
|
469
|
+
{agent?.name || 'Unassigned'} · {task.status === 'running' ? 'running' : 'queued'}{task.startedAt ? ` · ${timeAgo(task.startedAt, now)}` : ''}
|
|
456
470
|
</span>
|
|
457
471
|
</div>
|
|
458
472
|
</button>
|
|
@@ -482,7 +496,7 @@ export function HomeView() {
|
|
|
482
496
|
<div className="flex-1 min-w-0">
|
|
483
497
|
<span className="text-[13px] font-500 text-text truncate block">{sched.name}</span>
|
|
484
498
|
<span className="text-[11px] text-text-3/50">
|
|
485
|
-
{agent?.name || 'No agent'} · {sched.nextRunAt ? timeUntil(sched.nextRunAt) : '—'}
|
|
499
|
+
{agent?.name || 'No agent'} · {sched.nextRunAt ? timeUntil(sched.nextRunAt, now) : '—'}
|
|
486
500
|
</span>
|
|
487
501
|
</div>
|
|
488
502
|
</div>
|
|
@@ -503,7 +517,7 @@ export function HomeView() {
|
|
|
503
517
|
{pinnedAgents.map((agent) => {
|
|
504
518
|
const threadSession = agent.threadSessionId ? sessions[agent.threadSessionId] as Session | undefined : undefined
|
|
505
519
|
const heartbeatOn = agent.heartbeatEnabled === true && (agent.plugins?.length ?? 0) > 0
|
|
506
|
-
const recentlyActive = (threadSession?.lastActiveAt ?? 0) >
|
|
520
|
+
const recentlyActive = !!now && (threadSession?.lastActiveAt ?? 0) > now - 30 * 60 * 1000
|
|
507
521
|
const isOnline = runningAgentIds.has(agent.id) || (threadSession?.active ?? false) || heartbeatOn || recentlyActive
|
|
508
522
|
const isTyping = streamingSessionId === agent.threadSessionId
|
|
509
523
|
const lastActive = threadSession?.lastActiveAt || agent.lastUsedAt || agent.updatedAt
|
|
@@ -539,7 +553,7 @@ export function HomeView() {
|
|
|
539
553
|
</span>
|
|
540
554
|
) : (
|
|
541
555
|
<span className={`text-[10px] ${isOnline ? 'text-emerald-400/80' : 'text-text-3/50'}`}>
|
|
542
|
-
{isOnline ? 'Online' : lastActive ? timeAgo(lastActive) : 'Idle'}
|
|
556
|
+
{isOnline ? 'Online' : lastActive ? timeAgo(lastActive, now) : 'Idle'}
|
|
543
557
|
</span>
|
|
544
558
|
)}
|
|
545
559
|
{modelLabel && (
|
|
@@ -589,7 +603,7 @@ export function HomeView() {
|
|
|
589
603
|
{displayName}
|
|
590
604
|
</span>
|
|
591
605
|
<span className="text-[11px] text-text-3/50 shrink-0">
|
|
592
|
-
{timeAgo(session.lastActiveAt || session.createdAt)}
|
|
606
|
+
{timeAgo(session.lastActiveAt || session.createdAt, now)}
|
|
593
607
|
</span>
|
|
594
608
|
</div>
|
|
595
609
|
{lastMsg && (
|
|
@@ -619,7 +633,7 @@ export function HomeView() {
|
|
|
619
633
|
<path d={ACTIVITY_ICONS[entry.action] || ACTIVITY_ICONS.updated} />
|
|
620
634
|
</svg>
|
|
621
635
|
<span className="text-[12px] text-text-3/80 flex-1 truncate">{entry.summary}</span>
|
|
622
|
-
<span className="text-[10px] text-text-3/40 shrink-0">{timeAgo(entry.timestamp)}</span>
|
|
636
|
+
<span className="text-[10px] text-text-3/40 shrink-0">{timeAgo(entry.timestamp, now)}</span>
|
|
623
637
|
</div>
|
|
624
638
|
))}
|
|
625
639
|
</div>
|
|
@@ -10,6 +10,7 @@ import { FilePreview } from '@/components/shared/file-preview'
|
|
|
10
10
|
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
|
|
11
11
|
import { toast } from 'sonner'
|
|
12
12
|
import { safeStorageGet, safeStorageRemove, safeStorageSet } from '@/lib/safe-storage'
|
|
13
|
+
import { errorMessage } from '@/lib/shared-utils'
|
|
13
14
|
|
|
14
15
|
interface Props {
|
|
15
16
|
streaming: boolean
|
|
@@ -119,7 +120,7 @@ export function ChatInput({ streaming, onSend, onStop, pluginChatActions = [] }:
|
|
|
119
120
|
const result = await uploadImage(file)
|
|
120
121
|
addPendingFile({ file, path: result.path, url: result.url })
|
|
121
122
|
} catch (err: unknown) {
|
|
122
|
-
console.error('File upload failed:',
|
|
123
|
+
console.error('File upload failed:', errorMessage(err))
|
|
123
124
|
}
|
|
124
125
|
}, [addPendingFile])
|
|
125
126
|
|
|
@@ -161,6 +162,8 @@ export function ChatInput({ streaming, onSend, onStop, pluginChatActions = [] }:
|
|
|
161
162
|
</div>
|
|
162
163
|
<button
|
|
163
164
|
onClick={onStop}
|
|
165
|
+
aria-label="Stop response"
|
|
166
|
+
data-testid="chat-stop"
|
|
164
167
|
className="px-4 py-2 rounded-pill border border-danger/20 bg-danger/[0.06]
|
|
165
168
|
text-danger text-[12px] font-600 cursor-pointer transition-all duration-200
|
|
166
169
|
active:scale-95 hover:bg-danger/[0.1] hover:border-danger/30 shrink-0"
|
|
@@ -210,6 +213,8 @@ export function ChatInput({ streaming, onSend, onStop, pluginChatActions = [] }:
|
|
|
210
213
|
onKeyDown={handleKeyDown}
|
|
211
214
|
onPaste={handlePaste}
|
|
212
215
|
placeholder="Ask me anything..."
|
|
216
|
+
aria-label="Message input"
|
|
217
|
+
data-testid="chat-input"
|
|
213
218
|
rows={1}
|
|
214
219
|
className="w-full px-5 pt-4 pb-2 bg-transparent text-text text-[15px] outline-none resize-none
|
|
215
220
|
max-h-[140px] leading-[1.55] placeholder:text-text-3/70 border-none"
|
|
@@ -220,6 +225,8 @@ export function ChatInput({ streaming, onSend, onStop, pluginChatActions = [] }:
|
|
|
220
225
|
<button
|
|
221
226
|
type="button"
|
|
222
227
|
onClick={() => setExtrasOpen((open) => !open)}
|
|
228
|
+
aria-label="Add attachment"
|
|
229
|
+
data-testid="chat-add"
|
|
223
230
|
className="flex items-center gap-1.5 px-3 py-2 rounded-[10px] border-none bg-transparent
|
|
224
231
|
text-text-3 text-[13px] cursor-pointer hover:text-text-2 hover:bg-white/[0.05] transition-all duration-200"
|
|
225
232
|
style={{ fontFamily: 'inherit' }}
|
|
@@ -240,6 +247,8 @@ export function ChatInput({ streaming, onSend, onStop, pluginChatActions = [] }:
|
|
|
240
247
|
<button
|
|
241
248
|
onClick={handleSend}
|
|
242
249
|
disabled={!hasContent}
|
|
250
|
+
aria-label={streaming ? 'Queue message' : 'Send message'}
|
|
251
|
+
data-testid="chat-send"
|
|
243
252
|
className={`w-9 h-9 rounded-[11px] border-none flex items-center justify-center
|
|
244
253
|
shrink-0 cursor-pointer transition-all duration-250
|
|
245
254
|
${hasContent
|
|
@@ -10,49 +10,31 @@ import { SettingsPage } from '@/components/shared/settings/settings-page'
|
|
|
10
10
|
import { AgentList } from '@/components/agents/agent-list'
|
|
11
11
|
import { AgentChatList } from '@/components/agents/agent-chat-list'
|
|
12
12
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
13
|
-
import { AgentSheet } from '@/components/agents/agent-sheet'
|
|
14
13
|
import { ScheduleList } from '@/components/schedules/schedule-list'
|
|
15
|
-
import { ScheduleSheet } from '@/components/schedules/schedule-sheet'
|
|
16
14
|
import { MemoryAgentList } from '@/components/memory/memory-agent-list'
|
|
17
|
-
import { MemorySheet } from '@/components/memory/memory-sheet'
|
|
18
15
|
import { MemoryBrowser } from '@/components/memory/memory-browser'
|
|
19
16
|
import { TaskList } from '@/components/tasks/task-list'
|
|
20
|
-
import { TaskSheet } from '@/components/tasks/task-sheet'
|
|
21
17
|
import { TaskBoard } from '@/components/tasks/task-board'
|
|
22
18
|
import { ApprovalsPanel } from '@/components/tasks/approvals-panel'
|
|
23
19
|
import { SecretsList } from '@/components/secrets/secrets-list'
|
|
24
|
-
import { SecretSheet } from '@/components/secrets/secret-sheet'
|
|
25
20
|
import { ProviderList } from '@/components/providers/provider-list'
|
|
26
|
-
import { ProviderSheet } from '@/components/providers/provider-sheet'
|
|
27
|
-
import { GatewaySheet } from '@/components/gateways/gateway-sheet'
|
|
28
21
|
import { SkillList } from '@/components/skills/skill-list'
|
|
29
|
-
import { SkillSheet } from '@/components/skills/skill-sheet'
|
|
30
22
|
import { ConnectorList } from '@/components/connectors/connector-list'
|
|
31
|
-
import { ConnectorSheet } from '@/components/connectors/connector-sheet'
|
|
32
23
|
import { ChatroomList } from '@/components/chatrooms/chatroom-list'
|
|
33
24
|
import { ChatroomView } from '@/components/chatrooms/chatroom-view'
|
|
34
|
-
import { ChatroomSheet } from '@/components/chatrooms/chatroom-sheet'
|
|
35
25
|
import { useChatroomStore } from '@/stores/use-chatroom-store'
|
|
36
26
|
import { WebhookList } from '@/components/webhooks/webhook-list'
|
|
37
|
-
import { WebhookSheet } from '@/components/webhooks/webhook-sheet'
|
|
38
27
|
import { LogList } from '@/components/logs/log-list'
|
|
39
28
|
import { McpServerList } from '@/components/mcp-servers/mcp-server-list'
|
|
40
|
-
import { McpServerSheet } from '@/components/mcp-servers/mcp-server-sheet'
|
|
41
29
|
import { KnowledgeList } from '@/components/knowledge/knowledge-list'
|
|
42
|
-
import { KnowledgeSheet } from '@/components/knowledge/knowledge-sheet'
|
|
43
30
|
import { PluginList } from '@/components/plugins/plugin-list'
|
|
44
|
-
import { PluginSheet } from '@/components/plugins/plugin-sheet'
|
|
45
31
|
import { RunList } from '@/components/runs/run-list'
|
|
46
32
|
import { ActivityFeed } from '@/components/activity/activity-feed'
|
|
47
33
|
import { MetricsDashboard } from '@/components/usage/metrics-dashboard'
|
|
48
34
|
import { WalletPanel } from '@/components/wallets/wallet-panel'
|
|
49
35
|
import { ProjectList } from '@/components/projects/project-list'
|
|
50
36
|
import { ProjectDetail } from '@/components/projects/project-detail'
|
|
51
|
-
import {
|
|
52
|
-
import { SearchDialog } from '@/components/shared/search-dialog'
|
|
53
|
-
import { AgentSwitchDialog } from '@/components/shared/agent-switch-dialog'
|
|
54
|
-
import { KeyboardShortcutsDialog } from '@/components/shared/keyboard-shortcuts-dialog'
|
|
55
|
-
import { ProfileSheet } from '@/components/shared/profile-sheet'
|
|
37
|
+
import { SheetLayer } from './sheet-layer'
|
|
56
38
|
import { HomeView } from '@/components/home/home-view'
|
|
57
39
|
import { NetworkBanner } from './network-banner'
|
|
58
40
|
import { UpdateBanner } from './update-banner'
|
|
@@ -1106,25 +1088,7 @@ export function AppLayout() {
|
|
|
1106
1088
|
</ErrorBoundary>
|
|
1107
1089
|
|
|
1108
1090
|
<CommandPalette />
|
|
1109
|
-
<
|
|
1110
|
-
<AgentSwitchDialog />
|
|
1111
|
-
<KeyboardShortcutsDialog />
|
|
1112
|
-
<AgentSheet />
|
|
1113
|
-
<ScheduleSheet />
|
|
1114
|
-
<MemorySheet />
|
|
1115
|
-
<TaskSheet />
|
|
1116
|
-
<SecretSheet />
|
|
1117
|
-
<ProviderSheet />
|
|
1118
|
-
<GatewaySheet />
|
|
1119
|
-
<SkillSheet />
|
|
1120
|
-
<ConnectorSheet />
|
|
1121
|
-
<ChatroomSheet />
|
|
1122
|
-
<WebhookSheet />
|
|
1123
|
-
<McpServerSheet />
|
|
1124
|
-
<KnowledgeSheet />
|
|
1125
|
-
<PluginSheet />
|
|
1126
|
-
<ProjectSheet />
|
|
1127
|
-
<ProfileSheet open={profileSheetOpen} onClose={() => setProfileSheetOpen(false)} />
|
|
1091
|
+
<SheetLayer profileSheetOpen={profileSheetOpen} setProfileSheetOpen={setProfileSheetOpen} />
|
|
1128
1092
|
|
|
1129
1093
|
</div>
|
|
1130
1094
|
)
|