@swarmclawai/swarmclaw 0.8.4 → 0.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/bin/swarmclaw.js +5 -1
- package/bin/worker-cmd.js +73 -0
- package/package.json +2 -1
- package/src/app/api/agents/[id]/route.ts +17 -7
- package/src/app/api/agents/route.ts +21 -8
- package/src/app/api/approvals/route.test.ts +6 -6
- package/src/app/api/approvals/route.ts +2 -1
- package/src/app/api/auth/route.ts +2 -3
- package/src/app/api/chatrooms/[id]/chat/route.test.ts +299 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +3 -2
- package/src/app/api/chatrooms/[id]/route.ts +7 -6
- package/src/app/api/chats/[id]/chat/route.test.ts +496 -0
- package/src/app/api/chats/[id]/chat/route.ts +7 -3
- package/src/app/api/chats/[id]/clear/route.ts +9 -9
- package/src/app/api/chats/[id]/devserver/route.ts +2 -1
- package/src/app/api/chats/[id]/edit-resend/route.ts +3 -4
- package/src/app/api/chats/[id]/fork/route.ts +3 -5
- package/src/app/api/chats/[id]/restore/route.ts +6 -7
- package/src/app/api/chats/[id]/retry/route.ts +3 -4
- package/src/app/api/chats/[id]/route.ts +61 -62
- package/src/app/api/chats/route.ts +7 -1
- package/src/app/api/connectors/[id]/route.ts +7 -8
- package/src/app/api/connectors/route.ts +5 -4
- package/src/app/api/eval/run/route.ts +2 -1
- package/src/app/api/eval/suite/route.ts +2 -1
- package/src/app/api/external-agents/route.test.ts +1 -1
- package/src/app/api/external-agents/route.ts +2 -2
- package/src/app/api/files/serve/route.ts +1 -1
- package/src/app/api/gateways/[id]/route.ts +7 -5
- package/src/app/api/gateways/route.ts +1 -1
- package/src/app/api/knowledge/upload/route.ts +1 -1
- package/src/app/api/logs/route.ts +5 -7
- package/src/app/api/memory-images/[filename]/route.ts +2 -3
- package/src/app/api/openclaw/agent-files/route.ts +4 -3
- package/src/app/api/openclaw/approvals/route.ts +3 -4
- package/src/app/api/openclaw/config-sync/route.ts +3 -2
- package/src/app/api/openclaw/cron/route.ts +3 -2
- package/src/app/api/openclaw/dotenv-keys/route.ts +2 -1
- package/src/app/api/openclaw/exec-config/route.ts +3 -2
- package/src/app/api/openclaw/gateway/route.ts +5 -4
- package/src/app/api/openclaw/history/route.ts +3 -2
- package/src/app/api/openclaw/media/route.ts +2 -1
- package/src/app/api/openclaw/permissions/route.ts +3 -2
- package/src/app/api/openclaw/sandbox-env/route.ts +3 -2
- package/src/app/api/openclaw/skills/install/route.ts +2 -1
- package/src/app/api/openclaw/skills/remove/route.ts +2 -1
- package/src/app/api/openclaw/skills/route.ts +3 -2
- package/src/app/api/orchestrator/run/route.ts +5 -14
- package/src/app/api/perf/route.ts +43 -0
- package/src/app/api/plugins/dependencies/route.ts +2 -1
- package/src/app/api/plugins/install/route.ts +2 -1
- package/src/app/api/plugins/marketplace/route.ts +3 -2
- package/src/app/api/plugins/settings/route.ts +2 -1
- package/src/app/api/preview-server/route.ts +11 -10
- package/src/app/api/projects/[id]/route.ts +1 -1
- package/src/app/api/schedules/[id]/route.test.ts +128 -0
- package/src/app/api/schedules/[id]/route.ts +43 -43
- package/src/app/api/schedules/[id]/run/route.ts +11 -62
- package/src/app/api/schedules/route.ts +21 -87
- package/src/app/api/settings/route.ts +2 -0
- package/src/app/api/setup/doctor/route.ts +9 -8
- package/src/app/api/tasks/[id]/approve/route.ts +33 -30
- package/src/app/api/tasks/[id]/route.ts +12 -35
- package/src/app/api/tasks/import/github/route.ts +2 -1
- package/src/app/api/tasks/route.ts +79 -91
- package/src/app/api/wallets/[id]/approve/route.ts +2 -1
- package/src/app/api/wallets/[id]/route.ts +13 -19
- package/src/app/api/wallets/[id]/send/route.ts +2 -1
- package/src/app/api/wallets/route.ts +2 -1
- package/src/app/api/webhooks/[id]/route.ts +2 -1
- package/src/app/api/webhooks/route.test.ts +3 -1
- package/src/app/page.tsx +23 -331
- package/src/cli/index.js +19 -0
- package/src/cli/index.ts +38 -7
- package/src/cli/spec.js +9 -0
- package/src/components/activity/activity-feed.tsx +7 -4
- package/src/components/agents/agent-card.tsx +32 -6
- package/src/components/agents/agent-chat-list.tsx +55 -22
- package/src/components/agents/agent-files-editor.tsx +3 -2
- package/src/components/agents/agent-sheet.tsx +123 -22
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/agents/openclaw-skills-panel.tsx +2 -1
- package/src/components/agents/trash-list.tsx +1 -1
- package/src/components/auth/access-key-gate.tsx +8 -2
- package/src/components/auth/setup-wizard.tsx +10 -9
- package/src/components/auth/user-picker.tsx +3 -2
- package/src/components/chat/chat-area.tsx +20 -1
- package/src/components/chat/chat-card.tsx +18 -3
- package/src/components/chat/chat-header.tsx +24 -4
- package/src/components/chat/chat-list.tsx +2 -11
- package/src/components/chat/heartbeat-history-panel.tsx +2 -1
- package/src/components/chat/message-bubble.tsx +45 -6
- package/src/components/chat/message-list.tsx +280 -145
- package/src/components/chat/streaming-bubble.tsx +217 -60
- package/src/components/chat/swarm-panel.test.ts +274 -0
- package/src/components/chat/swarm-panel.tsx +410 -0
- package/src/components/chat/swarm-status-card.tsx +346 -0
- package/src/components/chat/tool-call-bubble.tsx +48 -23
- package/src/components/chatrooms/chatroom-list.tsx +8 -5
- package/src/components/chatrooms/chatroom-message.tsx +10 -7
- package/src/components/chatrooms/chatroom-view.tsx +12 -9
- package/src/components/connectors/connector-health.tsx +6 -4
- package/src/components/connectors/connector-list.tsx +16 -11
- package/src/components/connectors/connector-sheet.tsx +12 -6
- package/src/components/home/home-view.tsx +38 -24
- package/src/components/input/chat-input.tsx +10 -1
- package/src/components/layout/app-layout.tsx +2 -38
- package/src/components/layout/sheet-layer.tsx +50 -0
- package/src/components/mcp-servers/mcp-server-list.tsx +37 -5
- package/src/components/mcp-servers/mcp-server-sheet.tsx +12 -2
- package/src/components/plugins/plugin-list.tsx +8 -4
- package/src/components/plugins/plugin-sheet.tsx +2 -1
- package/src/components/providers/provider-list.tsx +3 -2
- package/src/components/providers/provider-sheet.tsx +2 -1
- package/src/components/runs/run-list.tsx +11 -7
- package/src/components/schedules/schedule-card.tsx +5 -3
- package/src/components/shared/agent-switch-dialog.tsx +1 -1
- package/src/components/shared/attachment-chip.tsx +19 -3
- package/src/components/shared/notification-center.tsx +6 -3
- package/src/components/shared/settings/plugin-manager.tsx +3 -2
- package/src/components/shared/settings/section-embedding.tsx +2 -1
- package/src/components/shared/settings/section-orchestrator.tsx +2 -1
- package/src/components/shared/settings/section-user-preferences.tsx +107 -0
- package/src/components/shared/settings/settings-page.tsx +13 -9
- package/src/components/skills/clawhub-browser.tsx +15 -4
- package/src/components/skills/skill-list.tsx +15 -4
- package/src/components/tasks/approvals-panel.tsx +2 -1
- package/src/components/tasks/task-board.tsx +35 -37
- package/src/components/tasks/task-sheet.tsx +4 -3
- package/src/components/ui/full-screen-loader.tsx +164 -0
- package/src/components/wallets/wallet-approval-dialog.tsx +2 -1
- package/src/components/wallets/wallet-panel.tsx +6 -5
- package/src/components/wallets/wallet-section.tsx +3 -2
- package/src/components/webhooks/webhook-list.tsx +4 -5
- package/src/components/webhooks/webhook-sheet.tsx +6 -6
- package/src/hooks/use-app-bootstrap.ts +202 -0
- package/src/hooks/use-mounted-ref.ts +14 -0
- package/src/hooks/use-now.ts +31 -0
- package/src/hooks/use-openclaw-gateway.ts +2 -1
- package/src/instrumentation.ts +20 -8
- package/src/lib/agent-default-tools.test.ts +52 -0
- package/src/lib/agent-default-tools.ts +40 -0
- package/src/lib/api-client.test.ts +21 -0
- package/src/lib/api-client.ts +6 -11
- package/src/lib/canvas-content.test.ts +360 -0
- package/src/lib/chat-streaming-state.test.ts +49 -2
- package/src/lib/chat-streaming-state.ts +26 -10
- package/src/lib/fetch-timeout.test.ts +54 -0
- package/src/lib/fetch-timeout.ts +60 -3
- package/src/lib/live-tool-events.test.ts +77 -0
- package/src/lib/live-tool-events.ts +73 -0
- package/src/lib/local-observability.test.ts +2 -2
- package/src/lib/openclaw-endpoint.test.ts +1 -1
- package/src/lib/providers/anthropic.ts +12 -16
- package/src/lib/providers/index.ts +4 -2
- package/src/lib/providers/ollama.ts +9 -6
- package/src/lib/providers/openai.ts +11 -14
- package/src/lib/runtime-env.test.ts +8 -8
- package/src/lib/schedule-dedupe-advanced.test.ts +2 -2
- package/src/lib/schedule-dedupe.test.ts +1 -1
- package/src/lib/schedule-dedupe.ts +3 -2
- package/src/lib/server/agent-thread-session.test.ts +6 -6
- package/src/lib/server/agent-thread-session.ts +6 -9
- package/src/lib/server/alert-dispatch.ts +2 -1
- package/src/lib/server/api-routes.test.ts +6 -6
- package/src/lib/server/approval-connector-notify.test.ts +4 -4
- package/src/lib/server/approvals-auto-approve.test.ts +29 -29
- package/src/lib/server/approvals.test.ts +317 -0
- package/src/lib/server/approvals.ts +5 -4
- package/src/lib/server/autonomy-runtime.test.ts +11 -11
- package/src/lib/server/browser-state.ts +2 -2
- package/src/lib/server/capability-router.test.ts +1 -1
- package/src/lib/server/capability-router.ts +3 -2
- package/src/lib/server/chat-execution-advanced.test.ts +15 -2
- package/src/lib/server/chat-execution-connector-delivery.ts +67 -0
- package/src/lib/server/chat-execution-disabled.test.ts +3 -3
- package/src/lib/server/chat-execution-eval-history.test.ts +3 -3
- package/src/lib/server/chat-execution-heartbeat.test.ts +42 -1
- package/src/lib/server/chat-execution-session-sync.test.ts +119 -0
- package/src/lib/server/chat-execution-tool-events.ts +116 -0
- package/src/lib/server/chat-execution-utils.test.ts +479 -0
- package/src/lib/server/chat-execution-utils.ts +533 -0
- package/src/lib/server/chat-execution.ts +153 -748
- package/src/lib/server/chat-streaming-utils.ts +174 -0
- package/src/lib/server/chat-turn-tool-routing.ts +310 -0
- package/src/lib/server/chatroom-session-persistence.test.ts +2 -2
- package/src/lib/server/clawhub-client.ts +2 -1
- package/src/lib/server/collection-helpers.test.ts +92 -0
- package/src/lib/server/collection-helpers.ts +25 -3
- package/src/lib/server/connectors/access.ts +146 -0
- package/src/lib/server/connectors/bluebubbles.test.ts +1 -1
- package/src/lib/server/connectors/bluebubbles.ts +4 -4
- package/src/lib/server/connectors/commands.ts +367 -0
- package/src/lib/server/connectors/connector-routing.test.ts +4 -4
- package/src/lib/server/connectors/delivery.ts +142 -0
- package/src/lib/server/connectors/discord.ts +37 -40
- package/src/lib/server/connectors/email.ts +11 -10
- package/src/lib/server/connectors/googlechat.ts +4 -4
- package/src/lib/server/connectors/inbound-audio-transcription.ts +2 -1
- package/src/lib/server/connectors/ingress-delivery.ts +23 -0
- package/src/lib/server/connectors/manager-roundtrip.test.ts +300 -0
- package/src/lib/server/connectors/manager.test.ts +352 -77
- package/src/lib/server/connectors/manager.ts +134 -673
- package/src/lib/server/connectors/matrix.ts +4 -4
- package/src/lib/server/connectors/message-sentinel.ts +7 -0
- package/src/lib/server/connectors/openclaw.test.ts +1 -1
- package/src/lib/server/connectors/openclaw.ts +8 -10
- package/src/lib/server/connectors/outbox.test.ts +192 -0
- package/src/lib/server/connectors/outbox.ts +369 -0
- package/src/lib/server/connectors/pairing.test.ts +18 -1
- package/src/lib/server/connectors/pairing.ts +49 -4
- package/src/lib/server/connectors/policy.ts +9 -3
- package/src/lib/server/connectors/reconnect-state.ts +71 -0
- package/src/lib/server/connectors/response-media.ts +256 -0
- package/src/lib/server/connectors/runtime-state.ts +67 -0
- package/src/lib/server/connectors/session.test.ts +357 -0
- package/src/lib/server/connectors/session.ts +422 -0
- package/src/lib/server/connectors/signal.ts +7 -7
- package/src/lib/server/connectors/slack.ts +43 -43
- package/src/lib/server/connectors/teams.ts +4 -4
- package/src/lib/server/connectors/telegram.ts +37 -43
- package/src/lib/server/connectors/types.ts +31 -1
- package/src/lib/server/connectors/whatsapp.test.ts +108 -0
- package/src/lib/server/connectors/whatsapp.ts +106 -34
- package/src/lib/server/context-manager.test.ts +409 -0
- package/src/lib/server/cost.test.ts +1 -1
- package/src/lib/server/daemon-policy.ts +78 -0
- package/src/lib/server/daemon-state-connectors.test.ts +167 -0
- package/src/lib/server/daemon-state.test.ts +283 -55
- package/src/lib/server/daemon-state.ts +106 -109
- package/src/lib/server/data-dir.test.ts +5 -5
- package/src/lib/server/data-dir.ts +4 -0
- package/src/lib/server/delegation-jobs-advanced.test.ts +1 -1
- package/src/lib/server/delegation-jobs.test.ts +87 -0
- package/src/lib/server/delegation-jobs.ts +42 -48
- package/src/lib/server/devserver-launch.ts +1 -1
- package/src/lib/server/document-utils.ts +7 -9
- package/src/lib/server/elevenlabs.ts +2 -1
- package/src/lib/server/embeddings.test.ts +105 -0
- package/src/lib/server/ethereum.ts +3 -2
- package/src/lib/server/eval/agent-regression.ts +3 -2
- package/src/lib/server/eval/runner.ts +2 -1
- package/src/lib/server/eval/scorer.ts +2 -1
- package/src/lib/server/evm-swap.ts +2 -1
- package/src/lib/server/gateway/protocol.test.ts +1 -1
- package/src/lib/server/guardian.ts +2 -1
- package/src/lib/server/heartbeat-blocked-suppression.test.ts +151 -0
- package/src/lib/server/heartbeat-service-timer.test.ts +6 -6
- package/src/lib/server/heartbeat-service.test.ts +406 -0
- package/src/lib/server/heartbeat-service.ts +54 -7
- package/src/lib/server/heartbeat-wake.test.ts +19 -0
- package/src/lib/server/heartbeat-wake.ts +17 -16
- package/src/lib/server/integrity-monitor.test.ts +149 -0
- package/src/lib/server/json-utils.ts +22 -0
- package/src/lib/server/knowledge-db.test.ts +13 -13
- package/src/lib/server/link-understanding.ts +2 -1
- package/src/lib/server/llm-response-cache.test.ts +1 -1
- package/src/lib/server/main-agent-loop-advanced.test.ts +65 -3
- package/src/lib/server/main-agent-loop.test.ts +6 -6
- package/src/lib/server/main-agent-loop.ts +21 -7
- package/src/lib/server/mcp-client.test.ts +1 -1
- package/src/lib/server/mcp-conformance.test.ts +1 -1
- package/src/lib/server/mcp-conformance.ts +3 -2
- package/src/lib/server/memory-consolidation.ts +2 -1
- package/src/lib/server/memory-db.test.ts +485 -0
- package/src/lib/server/memory-db.ts +39 -26
- package/src/lib/server/memory-graph.test.ts +2 -2
- package/src/lib/server/memory-policy.test.ts +7 -7
- package/src/lib/server/memory-retrieval.test.ts +1 -1
- package/src/lib/server/openclaw-config-sync.ts +2 -1
- package/src/lib/server/openclaw-deploy.test.ts +1 -1
- package/src/lib/server/openclaw-deploy.ts +8 -12
- package/src/lib/server/openclaw-exec-config.ts +2 -1
- package/src/lib/server/openclaw-gateway.ts +6 -7
- package/src/lib/server/openclaw-skills-normalize.ts +2 -1
- package/src/lib/server/openclaw-sync.ts +7 -5
- package/src/lib/server/orchestrator-lg-structure.test.ts +17 -0
- package/src/lib/server/orchestrator-lg.ts +199 -327
- package/src/lib/server/path-utils.ts +31 -0
- package/src/lib/server/perf.ts +161 -0
- package/src/lib/server/plugins-approval-guidance.ts +115 -0
- package/src/lib/server/plugins.test.ts +1 -1
- package/src/lib/server/plugins.ts +22 -132
- package/src/lib/server/process-manager.ts +5 -8
- package/src/lib/server/provider-health.test.ts +137 -0
- package/src/lib/server/provider-health.ts +3 -3
- package/src/lib/server/provider-model-discovery.ts +3 -12
- package/src/lib/server/queue-followups.test.ts +9 -9
- package/src/lib/server/queue-reconcile.test.ts +2 -2
- package/src/lib/server/queue-recovery.test.ts +269 -0
- package/src/lib/server/queue.test.ts +570 -0
- package/src/lib/server/queue.ts +62 -455
- package/src/lib/server/resolve-image.ts +30 -0
- package/src/lib/server/runtime-settings.test.ts +4 -4
- package/src/lib/server/runtime-storage-write-paths.test.ts +60 -0
- package/src/lib/server/schedule-normalization.test.ts +279 -0
- package/src/lib/server/schedule-service.ts +263 -0
- package/src/lib/server/scheduler.ts +17 -74
- package/src/lib/server/session-mailbox.test.ts +191 -0
- package/src/lib/server/session-run-manager.test.ts +640 -0
- package/src/lib/server/session-run-manager.ts +59 -15
- package/src/lib/server/session-tools/autonomy-tools.test.ts +20 -20
- package/src/lib/server/session-tools/calendar.ts +2 -1
- package/src/lib/server/session-tools/canvas.ts +2 -1
- package/src/lib/server/session-tools/chatroom.ts +2 -1
- package/src/lib/server/session-tools/connector.ts +26 -28
- package/src/lib/server/session-tools/context-mgmt.ts +3 -2
- package/src/lib/server/session-tools/crawl.ts +4 -3
- package/src/lib/server/session-tools/crud.ts +105 -324
- package/src/lib/server/session-tools/delegate-fallback.test.ts +9 -9
- package/src/lib/server/session-tools/delegate.ts +6 -8
- package/src/lib/server/session-tools/discovery-approvals.test.ts +15 -15
- package/src/lib/server/session-tools/discovery.ts +4 -3
- package/src/lib/server/session-tools/document.ts +2 -1
- package/src/lib/server/session-tools/email.ts +2 -1
- package/src/lib/server/session-tools/extract.ts +2 -1
- package/src/lib/server/session-tools/file.ts +4 -3
- package/src/lib/server/session-tools/http.ts +2 -1
- package/src/lib/server/session-tools/human-loop.ts +2 -1
- package/src/lib/server/session-tools/image-gen.ts +4 -3
- package/src/lib/server/session-tools/index.ts +26 -30
- package/src/lib/server/session-tools/mailbox.ts +2 -1
- package/src/lib/server/session-tools/manage-connectors.test.ts +4 -4
- package/src/lib/server/session-tools/manage-schedules.test.ts +12 -12
- package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +5 -5
- package/src/lib/server/session-tools/manage-tasks.test.ts +2 -2
- package/src/lib/server/session-tools/monitor.ts +2 -1
- package/src/lib/server/session-tools/platform.ts +2 -1
- package/src/lib/server/session-tools/plugin-creator.ts +2 -1
- package/src/lib/server/session-tools/replicate.ts +3 -2
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +6 -6
- package/src/lib/server/session-tools/shell.ts +4 -9
- package/src/lib/server/session-tools/subagent.ts +322 -170
- package/src/lib/server/session-tools/table.ts +6 -5
- package/src/lib/server/session-tools/wallet-tool.test.ts +3 -3
- package/src/lib/server/session-tools/wallet.ts +7 -6
- package/src/lib/server/session-tools/web-browser-config.test.ts +1 -0
- package/src/lib/server/session-tools/web-utils.ts +317 -0
- package/src/lib/server/session-tools/web.ts +62 -328
- package/src/lib/server/skill-prompt-budget.test.ts +1 -1
- package/src/lib/server/skills-normalize.ts +2 -1
- package/src/lib/server/storage-item-access.test.ts +302 -0
- package/src/lib/server/storage.ts +366 -314
- package/src/lib/server/stream-agent-chat.test.ts +82 -3
- package/src/lib/server/stream-agent-chat.ts +146 -510
- package/src/lib/server/stream-continuation.ts +412 -0
- package/src/lib/server/subagent-lineage.test.ts +647 -0
- package/src/lib/server/subagent-lineage.ts +435 -0
- package/src/lib/server/subagent-runtime.test.ts +484 -0
- package/src/lib/server/subagent-runtime.ts +419 -0
- package/src/lib/server/subagent-swarm.test.ts +391 -0
- package/src/lib/server/subagent-swarm.ts +564 -0
- package/src/lib/server/system-events.ts +3 -3
- package/src/lib/server/task-followups.test.ts +491 -0
- package/src/lib/server/task-followups.ts +391 -0
- package/src/lib/server/task-lifecycle.test.ts +205 -0
- package/src/lib/server/task-lifecycle.ts +200 -0
- package/src/lib/server/task-quality-gate.test.ts +1 -1
- package/src/lib/server/task-resume.ts +208 -0
- package/src/lib/server/task-service.test.ts +108 -0
- package/src/lib/server/task-service.ts +264 -0
- package/src/lib/server/task-validation.test.ts +1 -1
- package/src/lib/server/test-utils/run-with-temp-data-dir.ts +42 -0
- package/src/lib/server/tool-capability-policy.test.ts +2 -2
- package/src/lib/server/tool-capability-policy.ts +3 -2
- package/src/lib/server/tool-planning.ts +2 -1
- package/src/lib/server/tool-retry.ts +2 -3
- package/src/lib/server/wake-dispatcher.test.ts +303 -0
- package/src/lib/server/wake-dispatcher.ts +318 -0
- package/src/lib/server/wake-mode.test.ts +161 -0
- package/src/lib/server/wake-mode.ts +174 -0
- package/src/lib/server/wallet-service.ts +8 -9
- package/src/lib/server/watch-jobs.ts +2 -1
- package/src/lib/server/workspace-context.ts +2 -2
- package/src/lib/shared-utils.test.ts +142 -0
- package/src/lib/shared-utils.ts +62 -0
- package/src/lib/tool-event-summary.ts +2 -1
- package/src/lib/view-routes.test.ts +100 -0
- package/src/lib/wallet.test.ts +322 -6
- package/src/proxy.test.ts +4 -4
- package/src/proxy.ts +2 -3
- package/src/stores/set-if-changed.ts +40 -0
- package/src/stores/slices/agent-slice.ts +111 -0
- package/src/stores/slices/auth-slice.ts +25 -0
- package/src/stores/slices/data-slice.ts +301 -0
- package/src/stores/slices/index.ts +7 -0
- package/src/stores/slices/session-slice.ts +112 -0
- package/src/stores/slices/task-slice.ts +63 -0
- package/src/stores/slices/ui-slice.ts +192 -0
- package/src/stores/use-app-store.ts +17 -822
- package/src/stores/use-approval-store.ts +2 -1
- package/src/stores/use-chat-store.ts +8 -1
- package/src/types/index.ts +10 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { memo, useCallback, useState } from 'react'
|
|
4
|
+
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
5
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Types (mirror server-side SwarmSnapshot for the UI)
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
type MemberStatus = 'initializing' | 'ready' | 'running' | 'waiting' | 'completed' | 'failed' | 'cancelled' | 'timed_out' | 'spawn_error'
|
|
12
|
+
type SwarmStatus = 'spawning' | 'running' | 'completed' | 'partial' | 'failed'
|
|
13
|
+
|
|
14
|
+
export interface SwarmMemberData {
|
|
15
|
+
index: number
|
|
16
|
+
agentId: string
|
|
17
|
+
agentName: string
|
|
18
|
+
jobId: string
|
|
19
|
+
sessionId: string
|
|
20
|
+
task: string
|
|
21
|
+
status: MemberStatus
|
|
22
|
+
resultPreview: string | null
|
|
23
|
+
error: string | null
|
|
24
|
+
durationMs: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SwarmStatusData {
|
|
28
|
+
swarmId: string
|
|
29
|
+
parentSessionId: string | null
|
|
30
|
+
parentAgentName: string
|
|
31
|
+
parentAgentSeed: string | null
|
|
32
|
+
parentAgentAvatarUrl?: string | null
|
|
33
|
+
status: SwarmStatus
|
|
34
|
+
createdAt: number
|
|
35
|
+
completedAt: number | null
|
|
36
|
+
memberCount: number
|
|
37
|
+
completedCount: number
|
|
38
|
+
failedCount: number
|
|
39
|
+
members: SwarmMemberData[]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Status config
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
const SWARM_STATUS_CONFIG: Record<SwarmStatus, { label: string; color: string; bg: string; border: string }> = {
|
|
47
|
+
spawning: { label: 'Spawning', color: '#818CF8', bg: 'rgba(99,102,241,0.06)', border: 'rgba(99,102,241,0.12)' },
|
|
48
|
+
running: { label: 'Running', color: '#818CF8', bg: 'rgba(99,102,241,0.06)', border: 'rgba(99,102,241,0.12)' },
|
|
49
|
+
completed:{ label: 'All completed', color: '#34D399', bg: 'rgba(52,211,153,0.05)', border: 'rgba(52,211,153,0.12)' },
|
|
50
|
+
partial: { label: 'Partial', color: '#FBBF24', bg: 'rgba(251,191,36,0.05)', border: 'rgba(251,191,36,0.12)' },
|
|
51
|
+
failed: { label: 'Failed', color: '#F43F5E', bg: 'rgba(244,63,94,0.05)', border: 'rgba(244,63,94,0.12)' },
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const MEMBER_STATUS_CONFIG: Record<MemberStatus, { label: string; color: string; dotColor: string }> = {
|
|
55
|
+
initializing: { label: 'Initializing', color: '#A78BFA', dotColor: '#A78BFA' },
|
|
56
|
+
ready: { label: 'Queued', color: '#818CF8', dotColor: '#818CF8' },
|
|
57
|
+
running: { label: 'Running', color: '#60A5FA', dotColor: '#60A5FA' },
|
|
58
|
+
waiting: { label: 'Waiting', color: '#818CF8', dotColor: '#818CF8' },
|
|
59
|
+
completed: { label: 'Completed', color: '#34D399', dotColor: '#34D399' },
|
|
60
|
+
failed: { label: 'Failed', color: '#F43F5E', dotColor: '#F43F5E' },
|
|
61
|
+
cancelled: { label: 'Cancelled', color: '#6B7280', dotColor: '#6B7280' },
|
|
62
|
+
timed_out: { label: 'Timed out', color: '#F59E0B', dotColor: '#F59E0B' },
|
|
63
|
+
spawn_error: { label: 'Spawn failed', color: '#F43F5E', dotColor: '#F43F5E' },
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Swarm icon
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
function SwarmIcon({ size = 16, color = '#818CF8' }: { size?: number; color?: string }) {
|
|
71
|
+
return (
|
|
72
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" className="shrink-0">
|
|
73
|
+
{/* Center bee */}
|
|
74
|
+
<circle cx="12" cy="12" r="2.5" fill={color} opacity="0.9" />
|
|
75
|
+
{/* Orbital bees */}
|
|
76
|
+
<circle cx="6" cy="8" r="1.5" fill={color} opacity="0.6" />
|
|
77
|
+
<circle cx="18" cy="8" r="1.5" fill={color} opacity="0.6" />
|
|
78
|
+
<circle cx="6" cy="16" r="1.5" fill={color} opacity="0.6" />
|
|
79
|
+
<circle cx="18" cy="16" r="1.5" fill={color} opacity="0.6" />
|
|
80
|
+
{/* Connection lines */}
|
|
81
|
+
<line x1="8" y1="9" x2="10" y2="11" stroke={color} strokeWidth="0.8" opacity="0.3" />
|
|
82
|
+
<line x1="16" y1="9" x2="14" y2="11" stroke={color} strokeWidth="0.8" opacity="0.3" />
|
|
83
|
+
<line x1="8" y1="15" x2="10" y2="13" stroke={color} strokeWidth="0.8" opacity="0.3" />
|
|
84
|
+
<line x1="16" y1="15" x2="14" y2="13" stroke={color} strokeWidth="0.8" opacity="0.3" />
|
|
85
|
+
</svg>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Member status dot
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
function StatusDot({ status }: { status: MemberStatus }) {
|
|
94
|
+
const cfg = MEMBER_STATUS_CONFIG[status]
|
|
95
|
+
const isActive = status === 'running' || status === 'initializing' || status === 'waiting'
|
|
96
|
+
return (
|
|
97
|
+
<span
|
|
98
|
+
className="shrink-0 rounded-full"
|
|
99
|
+
style={{
|
|
100
|
+
width: 7,
|
|
101
|
+
height: 7,
|
|
102
|
+
backgroundColor: cfg.dotColor,
|
|
103
|
+
...(isActive ? { animation: 'swarm-pulse 1.5s ease-in-out infinite' } : {}),
|
|
104
|
+
}}
|
|
105
|
+
/>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Member card
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
const SwarmMemberCard = memo(function SwarmMemberCard({
|
|
114
|
+
member,
|
|
115
|
+
agents,
|
|
116
|
+
}: {
|
|
117
|
+
member: SwarmMemberData
|
|
118
|
+
agents: Record<string, any>
|
|
119
|
+
}) {
|
|
120
|
+
const [expanded, setExpanded] = useState(false)
|
|
121
|
+
const cfg = MEMBER_STATUS_CONFIG[member.status]
|
|
122
|
+
const agent = agents[member.agentId]
|
|
123
|
+
const avatarSeed = agent?.avatarSeed || member.agentId
|
|
124
|
+
const avatarUrl = agent?.avatarUrl || null
|
|
125
|
+
const hasContent = !!(member.resultPreview || member.error)
|
|
126
|
+
|
|
127
|
+
const formatDuration = (ms: number) => {
|
|
128
|
+
if (ms < 1000) return `${ms}ms`
|
|
129
|
+
return `${(ms / 1000).toFixed(1)}s`
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<div
|
|
134
|
+
className="rounded-[10px] overflow-hidden transition-all"
|
|
135
|
+
style={{
|
|
136
|
+
background: 'rgba(255,255,255,0.015)',
|
|
137
|
+
border: `1px solid rgba(255,255,255,0.05)`,
|
|
138
|
+
animation: `swarm-member-in 0.35s cubic-bezier(0.16, 1, 0.3, 1) ${member.index * 80}ms both`,
|
|
139
|
+
}}
|
|
140
|
+
>
|
|
141
|
+
{/* Header */}
|
|
142
|
+
<button
|
|
143
|
+
type="button"
|
|
144
|
+
className="w-full flex items-center gap-2 px-3 py-2 bg-transparent border-none cursor-pointer text-left"
|
|
145
|
+
onClick={() => hasContent && setExpanded(!expanded)}
|
|
146
|
+
disabled={!hasContent}
|
|
147
|
+
>
|
|
148
|
+
<AgentAvatar seed={avatarSeed} avatarUrl={avatarUrl} name={member.agentName} size={20} />
|
|
149
|
+
<StatusDot status={member.status} />
|
|
150
|
+
<div className="flex flex-col gap-0 min-w-0 flex-1">
|
|
151
|
+
<span className="text-[11px] font-600 text-text-2 truncate">
|
|
152
|
+
{member.agentName}
|
|
153
|
+
</span>
|
|
154
|
+
<span className="text-[10px] truncate" style={{ color: cfg.color }}>
|
|
155
|
+
{cfg.label}
|
|
156
|
+
{member.durationMs > 0 && member.status !== 'running' && (
|
|
157
|
+
<span className="text-text-3/50 ml-1">
|
|
158
|
+
{formatDuration(member.durationMs)}
|
|
159
|
+
</span>
|
|
160
|
+
)}
|
|
161
|
+
</span>
|
|
162
|
+
</div>
|
|
163
|
+
{hasContent && (
|
|
164
|
+
<svg
|
|
165
|
+
width="12"
|
|
166
|
+
height="12"
|
|
167
|
+
viewBox="0 0 24 24"
|
|
168
|
+
fill="none"
|
|
169
|
+
stroke="currentColor"
|
|
170
|
+
strokeWidth="2"
|
|
171
|
+
strokeLinecap="round"
|
|
172
|
+
className="shrink-0 text-text-3/30 transition-transform"
|
|
173
|
+
style={{ transform: expanded ? 'rotate(180deg)' : 'rotate(0deg)' }}
|
|
174
|
+
>
|
|
175
|
+
<polyline points="6 9 12 15 18 9" />
|
|
176
|
+
</svg>
|
|
177
|
+
)}
|
|
178
|
+
</button>
|
|
179
|
+
|
|
180
|
+
{/* Expanded content */}
|
|
181
|
+
{expanded && hasContent && (
|
|
182
|
+
<div
|
|
183
|
+
className="px-3 pb-2.5 pt-0"
|
|
184
|
+
style={{ borderTop: '1px solid rgba(255,255,255,0.04)' }}
|
|
185
|
+
>
|
|
186
|
+
{member.error && (
|
|
187
|
+
<div className="text-[11px] text-rose-400/80 leading-relaxed break-words mt-1.5">
|
|
188
|
+
{member.error}
|
|
189
|
+
</div>
|
|
190
|
+
)}
|
|
191
|
+
{member.resultPreview && !member.error && (
|
|
192
|
+
<div className="text-[11px] text-text-3/70 leading-relaxed break-words mt-1.5 max-h-[120px] overflow-y-auto">
|
|
193
|
+
{member.resultPreview}
|
|
194
|
+
</div>
|
|
195
|
+
)}
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
199
|
+
)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// Aggregate summary bar
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
function SwarmSummaryBar({ data }: { data: SwarmStatusData }) {
|
|
207
|
+
const cfg = SWARM_STATUS_CONFIG[data.status]
|
|
208
|
+
const isTerminal = data.status === 'completed' || data.status === 'partial' || data.status === 'failed'
|
|
209
|
+
|
|
210
|
+
const formatDuration = (ms: number) => {
|
|
211
|
+
if (ms < 1000) return `${ms}ms`
|
|
212
|
+
if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`
|
|
213
|
+
return `${(ms / 60_000).toFixed(1)}m`
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const durationMs = data.completedAt
|
|
217
|
+
? data.completedAt - data.createdAt
|
|
218
|
+
: Date.now() - data.createdAt
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<div
|
|
222
|
+
className="flex items-center gap-2 px-3 py-2 rounded-[8px]"
|
|
223
|
+
style={{ background: `${cfg.color}08` }}
|
|
224
|
+
>
|
|
225
|
+
<span className="text-[11px] font-700" style={{ color: cfg.color }}>
|
|
226
|
+
{data.completedCount}/{data.memberCount} completed
|
|
227
|
+
</span>
|
|
228
|
+
{data.failedCount > 0 && (
|
|
229
|
+
<span className="text-[11px] font-600 text-rose-400/70">
|
|
230
|
+
{data.failedCount} failed
|
|
231
|
+
</span>
|
|
232
|
+
)}
|
|
233
|
+
{isTerminal && (
|
|
234
|
+
<span className="text-[10px] text-text-3/40 ml-auto">
|
|
235
|
+
{formatDuration(durationMs)}
|
|
236
|
+
</span>
|
|
237
|
+
)}
|
|
238
|
+
{!isTerminal && (
|
|
239
|
+
<span
|
|
240
|
+
className="text-[10px] ml-auto"
|
|
241
|
+
style={{ color: cfg.color, opacity: 0.6 }}
|
|
242
|
+
>
|
|
243
|
+
in progress...
|
|
244
|
+
</span>
|
|
245
|
+
)}
|
|
246
|
+
</div>
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
// Main component
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
|
|
254
|
+
export const SwarmStatusCard = memo(function SwarmStatusCard({
|
|
255
|
+
data,
|
|
256
|
+
}: {
|
|
257
|
+
data: SwarmStatusData
|
|
258
|
+
}) {
|
|
259
|
+
const cfg = SWARM_STATUS_CONFIG[data.status]
|
|
260
|
+
const agents = useAppStore((s) => s.agents || {})
|
|
261
|
+
const isActive = data.status === 'spawning' || data.status === 'running'
|
|
262
|
+
|
|
263
|
+
return (
|
|
264
|
+
<div
|
|
265
|
+
className="rounded-[14px] overflow-hidden"
|
|
266
|
+
style={{
|
|
267
|
+
background: cfg.bg,
|
|
268
|
+
border: `1px solid ${cfg.border}`,
|
|
269
|
+
animation: 'delegation-handoff-in 0.45s cubic-bezier(0.16, 1, 0.3, 1)',
|
|
270
|
+
}}
|
|
271
|
+
>
|
|
272
|
+
{/* Header */}
|
|
273
|
+
<div
|
|
274
|
+
className="flex items-center gap-2.5 px-4 py-3"
|
|
275
|
+
style={{
|
|
276
|
+
borderBottom: `1px solid ${cfg.border}`,
|
|
277
|
+
}}
|
|
278
|
+
>
|
|
279
|
+
<SwarmIcon size={18} color={cfg.color} />
|
|
280
|
+
<div className="shrink-0">
|
|
281
|
+
<AgentAvatar
|
|
282
|
+
seed={data.parentAgentSeed}
|
|
283
|
+
avatarUrl={data.parentAgentAvatarUrl}
|
|
284
|
+
name={data.parentAgentName}
|
|
285
|
+
size={22}
|
|
286
|
+
/>
|
|
287
|
+
</div>
|
|
288
|
+
<div className="flex flex-col gap-0 min-w-0 flex-1">
|
|
289
|
+
<span className="text-[12px] font-700" style={{ color: cfg.color }}>
|
|
290
|
+
Swarm spawned by {data.parentAgentName}
|
|
291
|
+
</span>
|
|
292
|
+
<span className="text-[10px] text-text-3/50">
|
|
293
|
+
{data.memberCount} agent{data.memberCount !== 1 ? 's' : ''}
|
|
294
|
+
{' · '}
|
|
295
|
+
{new Date(data.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
|
296
|
+
</span>
|
|
297
|
+
</div>
|
|
298
|
+
{isActive && (
|
|
299
|
+
<span
|
|
300
|
+
className="shrink-0 w-2 h-2 rounded-full"
|
|
301
|
+
style={{
|
|
302
|
+
backgroundColor: cfg.color,
|
|
303
|
+
animation: 'swarm-pulse 1.5s ease-in-out infinite',
|
|
304
|
+
}}
|
|
305
|
+
/>
|
|
306
|
+
)}
|
|
307
|
+
</div>
|
|
308
|
+
|
|
309
|
+
{/* Members grid */}
|
|
310
|
+
<div className="px-3 py-2.5 flex flex-col gap-1.5">
|
|
311
|
+
{data.members.map((member) => (
|
|
312
|
+
<SwarmMemberCard
|
|
313
|
+
key={member.index}
|
|
314
|
+
member={member}
|
|
315
|
+
agents={agents}
|
|
316
|
+
/>
|
|
317
|
+
))}
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
{/* Summary bar */}
|
|
321
|
+
<div className="px-3 pb-3">
|
|
322
|
+
<SwarmSummaryBar data={data} />
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
)
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
// CSS keyframes (inject once)
|
|
330
|
+
// ---------------------------------------------------------------------------
|
|
331
|
+
|
|
332
|
+
const SWARM_STYLES = `
|
|
333
|
+
@keyframes swarm-pulse {
|
|
334
|
+
0%, 100% { opacity: 1; transform: scale(1); }
|
|
335
|
+
50% { opacity: 0.5; transform: scale(0.85); }
|
|
336
|
+
}
|
|
337
|
+
@keyframes swarm-member-in {
|
|
338
|
+
from { opacity: 0; transform: translateY(6px) scale(0.97); }
|
|
339
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
340
|
+
}
|
|
341
|
+
`
|
|
342
|
+
|
|
343
|
+
/** Call this once in a layout or the component tree root */
|
|
344
|
+
export function SwarmStatusStyles() {
|
|
345
|
+
return <style dangerouslySetInnerHTML={{ __html: SWARM_STYLES }} />
|
|
346
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { memo, useMemo, useState } from 'react'
|
|
4
4
|
import type { ToolEvent } from '@/stores/use-chat-store'
|
|
5
5
|
import { useChatStore } from '@/stores/use-chat-store'
|
|
6
6
|
import { useAppStore } from '@/stores/use-app-store'
|
|
@@ -19,6 +19,7 @@ const TOOL_COLORS: Record<string, string> = {
|
|
|
19
19
|
create_spreadsheet: '#10B981',
|
|
20
20
|
web_search: '#3B82F6',
|
|
21
21
|
web_fetch: '#3B82F6',
|
|
22
|
+
spawn_subagent: '#8B5CF6',
|
|
22
23
|
delegate_to_agent: '#6366F1',
|
|
23
24
|
check_delegation_status: '#6366F1',
|
|
24
25
|
delegate_to_claude_code: '#6366F1',
|
|
@@ -72,6 +73,7 @@ export const TOOL_LABELS: Record<string, string> = {
|
|
|
72
73
|
claude_code: 'Claude Code',
|
|
73
74
|
codex_cli: 'Codex CLI',
|
|
74
75
|
opencode_cli: 'OpenCode CLI',
|
|
76
|
+
spawn_subagent: 'Subagent',
|
|
75
77
|
delegate_to_agent: 'Agent Delegation',
|
|
76
78
|
check_delegation_status: 'Check Delegation',
|
|
77
79
|
delegate_to_claude_code: 'Claude Code',
|
|
@@ -110,6 +112,7 @@ export const TOOL_DESCRIPTIONS: Record<string, string> = {
|
|
|
110
112
|
claude_code: 'Enable delegation to Claude Code CLI',
|
|
111
113
|
codex_cli: 'Enable delegation to OpenAI Codex CLI',
|
|
112
114
|
opencode_cli: 'Enable delegation to OpenCode CLI',
|
|
115
|
+
spawn_subagent: 'Spawn native subagents with lineage tracking and batch support',
|
|
113
116
|
delegate_to_agent: 'Delegate a task to another agent',
|
|
114
117
|
check_delegation_status: 'Check the status of a delegated task',
|
|
115
118
|
delegate_to_claude_code: 'Delegate complex coding tasks to Claude Code',
|
|
@@ -210,7 +213,7 @@ function formatToolOutput(toolName: string, raw: string): string {
|
|
|
210
213
|
}
|
|
211
214
|
|
|
212
215
|
/** Extract a human-readable preview from tool input */
|
|
213
|
-
function getInputPreview(name: string, input: string): string {
|
|
216
|
+
export function getInputPreview(name: string, input: string): string {
|
|
214
217
|
try {
|
|
215
218
|
let parsed = JSON.parse(input)
|
|
216
219
|
// Unwrap LangChain's { input: ... } wrapper
|
|
@@ -262,6 +265,25 @@ function getInputPreview(name: string, input: string): string {
|
|
|
262
265
|
}
|
|
263
266
|
}
|
|
264
267
|
|
|
268
|
+
export function getToolLabel(name: string, input: string): string {
|
|
269
|
+
if (name === 'browser') {
|
|
270
|
+
try {
|
|
271
|
+
let parsed = JSON.parse(input)
|
|
272
|
+
if (parsed?.input && Object.keys(parsed).length === 1) {
|
|
273
|
+
const inner = typeof parsed.input === 'string' ? JSON.parse(parsed.input) : parsed.input
|
|
274
|
+
if (typeof inner === 'object' && inner !== null) parsed = inner
|
|
275
|
+
}
|
|
276
|
+
const action = parsed?.action || ''
|
|
277
|
+
const sub = BROWSER_ACTION_LABELS[action]
|
|
278
|
+
return sub ? `Browser · ${sub}` : 'Browser'
|
|
279
|
+
} catch {
|
|
280
|
+
return 'Browser'
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return TOOL_LABELS[name] || name.replace(/_/g, ' ')
|
|
285
|
+
}
|
|
286
|
+
|
|
265
287
|
/** Extract embedded images, videos, PDFs, and file links from tool output */
|
|
266
288
|
export function extractMedia(output: string): { images: string[]; videos: string[]; pdfs: { name: string; url: string }[]; files: { name: string; url: string }[]; cleanText: string } {
|
|
267
289
|
const images: string[] = []
|
|
@@ -364,29 +386,14 @@ function TimeoutQuickFix({ event }: { event: ToolEvent }) {
|
|
|
364
386
|
)
|
|
365
387
|
}
|
|
366
388
|
|
|
367
|
-
export function ToolCallBubble({ event }: { event: ToolEvent }) {
|
|
389
|
+
export const ToolCallBubble = memo(function ToolCallBubble({ event }: { event: ToolEvent }) {
|
|
368
390
|
const [imgExpanded, setImgExpanded] = useState(false)
|
|
369
391
|
const isError = event.status === 'error'
|
|
370
392
|
const color = isError ? '#F43F5E' : (TOOL_COLORS[event.name] || '#6366F1')
|
|
371
393
|
const isRunning = event.status === 'running'
|
|
394
|
+
const statusLabel = isRunning ? 'Running' : (isError ? 'Failed' : 'Done')
|
|
372
395
|
|
|
373
|
-
|
|
374
|
-
const label = useMemo(() => {
|
|
375
|
-
if (event.name === 'browser') {
|
|
376
|
-
try {
|
|
377
|
-
let parsed = JSON.parse(event.input)
|
|
378
|
-
// Unwrap LangChain {input: "..."} wrapper — inner value is a stringified JSON
|
|
379
|
-
if (parsed?.input && Object.keys(parsed).length === 1) {
|
|
380
|
-
const inner = typeof parsed.input === 'string' ? JSON.parse(parsed.input) : parsed.input
|
|
381
|
-
if (typeof inner === 'object' && inner !== null) parsed = inner
|
|
382
|
-
}
|
|
383
|
-
const action = parsed?.action || ''
|
|
384
|
-
const sub = BROWSER_ACTION_LABELS[action]
|
|
385
|
-
return sub ? `Browser · ${sub}` : 'Browser'
|
|
386
|
-
} catch { return 'Browser' }
|
|
387
|
-
}
|
|
388
|
-
return TOOL_LABELS[event.name] || event.name.replace(/_/g, ' ')
|
|
389
|
-
}, [event.name, event.input])
|
|
396
|
+
const label = useMemo(() => getToolLabel(event.name, event.input), [event.input, event.name])
|
|
390
397
|
|
|
391
398
|
const inputPreview = useMemo(() => getInputPreview(event.name, event.input), [event.name, event.input])
|
|
392
399
|
const formattedInput = useMemo(() => formatJson(event.input), [event.input])
|
|
@@ -422,9 +429,15 @@ export function ToolCallBubble({ event }: { event: ToolEvent }) {
|
|
|
422
429
|
}
|
|
423
430
|
|
|
424
431
|
return (
|
|
425
|
-
<div
|
|
426
|
-
|
|
432
|
+
<div
|
|
433
|
+
className="w-full text-left"
|
|
434
|
+
data-testid="tool-call-card"
|
|
435
|
+
data-tool-name={event.name}
|
|
436
|
+
data-tool-status={event.status}
|
|
437
|
+
>
|
|
438
|
+
<details open={isError || isRunning} className="group/tool">
|
|
427
439
|
<summary
|
|
440
|
+
data-testid="tool-call-summary"
|
|
428
441
|
className="w-full text-left rounded-[12px] border bg-surface/80 backdrop-blur-sm transition-all duration-200 hover:bg-surface-2 cursor-pointer list-none [&::-webkit-details-marker]:hidden"
|
|
429
442
|
style={{ borderLeft: `3px solid ${color}`, borderColor: `${color}33` }}
|
|
430
443
|
>
|
|
@@ -462,6 +475,17 @@ export function ToolCallBubble({ event }: { event: ToolEvent }) {
|
|
|
462
475
|
{inputPreview}
|
|
463
476
|
</span>
|
|
464
477
|
)}
|
|
478
|
+
<span
|
|
479
|
+
className={`shrink-0 rounded-full border px-2 py-0.5 text-[10px] font-600 uppercase tracking-[0.08em] ${
|
|
480
|
+
isRunning
|
|
481
|
+
? 'border-current/20 bg-white/[0.05] text-text-3'
|
|
482
|
+
: isError
|
|
483
|
+
? 'border-rose-500/25 bg-rose-500/10 text-rose-300'
|
|
484
|
+
: 'border-emerald-500/25 bg-emerald-500/10 text-emerald-300'
|
|
485
|
+
}`}
|
|
486
|
+
>
|
|
487
|
+
{statusLabel}
|
|
488
|
+
</span>
|
|
465
489
|
{hasMedia && (
|
|
466
490
|
<span className="text-[10px] text-text-3/50 font-500 shrink-0 group-open/tool:hidden">
|
|
467
491
|
{media.images.length > 0 && `${media.images.length} image${media.images.length > 1 ? 's' : ''}`}
|
|
@@ -504,6 +528,7 @@ export function ToolCallBubble({ event }: { event: ToolEvent }) {
|
|
|
504
528
|
<div className="mt-2 flex flex-col gap-2">
|
|
505
529
|
{media.images.map((src, i) => (
|
|
506
530
|
<div key={i} className="relative group/img">
|
|
531
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
507
532
|
<img
|
|
508
533
|
src={src}
|
|
509
534
|
alt={`Screenshot ${i + 1}`}
|
|
@@ -605,4 +630,4 @@ export function ToolCallBubble({ event }: { event: ToolEvent }) {
|
|
|
605
630
|
)}
|
|
606
631
|
</div>
|
|
607
632
|
)
|
|
608
|
-
}
|
|
633
|
+
})
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
import { useEffect, useCallback, useMemo, useState } from 'react'
|
|
4
4
|
import { useChatroomStore } from '@/stores/use-chatroom-store'
|
|
5
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
|
+
import { useNow } from '@/hooks/use-now'
|
|
6
7
|
import { useWs } from '@/hooks/use-ws'
|
|
7
8
|
import type { Chatroom } from '@/types'
|
|
8
9
|
import { EmptyState } from '@/components/shared/empty-state'
|
|
9
10
|
|
|
10
|
-
function formatRoomTime(ts: number): string {
|
|
11
|
-
|
|
11
|
+
function formatRoomTime(ts: number, now: number | null): string {
|
|
12
|
+
if (!now) return 'recently'
|
|
13
|
+
const diff = now - ts
|
|
12
14
|
if (diff < 60_000) return 'Now'
|
|
13
15
|
if (diff < 3_600_000) return `${Math.max(1, Math.floor(diff / 60_000))}m`
|
|
14
16
|
if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h`
|
|
@@ -16,6 +18,7 @@ function formatRoomTime(ts: number): string {
|
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export function ChatroomList() {
|
|
21
|
+
const now = useNow()
|
|
19
22
|
const chatrooms = useChatroomStore((s) => s.chatrooms)
|
|
20
23
|
const currentChatroomId = useChatroomStore((s) => s.currentChatroomId)
|
|
21
24
|
const loadChatrooms = useChatroomStore((s) => s.loadChatrooms)
|
|
@@ -72,15 +75,15 @@ export function ChatroomList() {
|
|
|
72
75
|
|
|
73
76
|
const filtered = useMemo(() => {
|
|
74
77
|
const query = search.trim().toLowerCase()
|
|
75
|
-
const now = Date.now()
|
|
76
78
|
return enriched.filter((item) => {
|
|
77
79
|
if (query && !item.searchText.includes(query)) return false
|
|
80
|
+
if (!now) return filter === 'unread' ? item.unreadCount > 0 : true
|
|
78
81
|
if (filter === 'active') return now - item.chatroom.updatedAt < 3_600_000
|
|
79
82
|
if (filter === 'recent') return now - item.chatroom.updatedAt < 86_400_000
|
|
80
83
|
if (filter === 'unread') return item.unreadCount > 0
|
|
81
84
|
return true
|
|
82
85
|
})
|
|
83
|
-
}, [enriched, filter, search])
|
|
86
|
+
}, [enriched, filter, now, search])
|
|
84
87
|
|
|
85
88
|
return (
|
|
86
89
|
<div className="flex-1 overflow-y-auto">
|
|
@@ -170,7 +173,7 @@ export function ChatroomList() {
|
|
|
170
173
|
</span>
|
|
171
174
|
)}
|
|
172
175
|
<span className="ml-auto shrink-0 text-[10px] font-mono text-text-3/55">
|
|
173
|
-
{formatRoomTime(lastMsg?.time || chatroom.updatedAt)}
|
|
176
|
+
{formatRoomTime(lastMsg?.time || chatroom.updatedAt, now)}
|
|
174
177
|
</span>
|
|
175
178
|
</div>
|
|
176
179
|
<div className="mt-0.5 flex flex-wrap items-center gap-2 text-[11px] text-text-3">
|
|
@@ -5,6 +5,7 @@ import ReactMarkdown from 'react-markdown'
|
|
|
5
5
|
import remarkGfm from 'remark-gfm'
|
|
6
6
|
import rehypeHighlight from 'rehype-highlight'
|
|
7
7
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
8
|
+
import { useNow } from '@/hooks/use-now'
|
|
8
9
|
import { CodeBlock } from '@/components/chat/code-block'
|
|
9
10
|
import { ReactionPicker } from './reaction-picker'
|
|
10
11
|
import { ReplyQuote } from '@/components/shared/reply-quote'
|
|
@@ -40,8 +41,8 @@ interface Props {
|
|
|
40
41
|
momentOverlay?: React.ReactNode
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
function formatRelativeTime(ts: number): string {
|
|
44
|
-
|
|
44
|
+
function formatRelativeTime(ts: number, now: number | null): string {
|
|
45
|
+
if (!now) return 'recently'
|
|
45
46
|
const diffSec = Math.floor((now - ts) / 1000)
|
|
46
47
|
if (diffSec < 60) return 'just now'
|
|
47
48
|
const diffMin = Math.floor(diffSec / 60)
|
|
@@ -62,11 +63,11 @@ function getMemberRoleFromChatroom(chatroom: Chatroom | undefined, agentId: stri
|
|
|
62
63
|
return member?.role || 'member'
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
function isAgentMutedInChatroom(chatroom: Chatroom | undefined, agentId: string): boolean {
|
|
66
|
+
function isAgentMutedInChatroom(chatroom: Chatroom | undefined, agentId: string, now: number | null): boolean {
|
|
66
67
|
if (!chatroom?.members?.length) return false
|
|
67
68
|
const member = chatroom.members.find((m) => m.agentId === agentId)
|
|
68
69
|
if (!member?.mutedUntil) return false
|
|
69
|
-
return new Date(member.mutedUntil).getTime() >
|
|
70
|
+
return !!now && new Date(member.mutedUntil).getTime() > now
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
function roleBadgeStyle(role: string): { label: string; className: string } | null {
|
|
@@ -136,6 +137,7 @@ function renderChatroomAttachments(message: ChatroomMessage) {
|
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
export function ChatroomMessageBubble({ message, agents, onToggleReaction, onReply, onTogglePin, onTransfer, onDeleteMessage, onMuteAgent, onUnmuteAgent, onSetRole, chatroom, pinnedMessageIds, streamingAgentIds, messages, grouped: isGrouped, momentOverlay }: Props) {
|
|
140
|
+
const now = useNow({ enabled: false })
|
|
139
141
|
const [showPicker, setShowPicker] = useState(false)
|
|
140
142
|
const [showTransferPicker, setShowTransferPicker] = useState(false)
|
|
141
143
|
const [showModMenu, setShowModMenu] = useState(false)
|
|
@@ -213,7 +215,7 @@ export function ChatroomMessageBubble({ message, agents, onToggleReaction, onRep
|
|
|
213
215
|
{!isGrouped && (() => {
|
|
214
216
|
const role = !isUser ? getMemberRoleFromChatroom(chatroom, message.senderId) : 'member'
|
|
215
217
|
const badge = !isUser ? roleBadgeStyle(role) : null
|
|
216
|
-
const muted = !isUser ? isAgentMutedInChatroom(chatroom, message.senderId) : false
|
|
218
|
+
const muted = !isUser ? isAgentMutedInChatroom(chatroom, message.senderId, now) : false
|
|
217
219
|
return (
|
|
218
220
|
<div className="flex items-baseline gap-2 mb-0.5">
|
|
219
221
|
{!isUser && agent ? (
|
|
@@ -241,7 +243,7 @@ export function ChatroomMessageBubble({ message, agents, onToggleReaction, onRep
|
|
|
241
243
|
Muted
|
|
242
244
|
</span>
|
|
243
245
|
)}
|
|
244
|
-
<span className="label-mono" title={new Date(message.time).
|
|
246
|
+
<span className="label-mono" title={new Date(message.time).toISOString()}>{formatRelativeTime(message.time, now)}</span>
|
|
245
247
|
</div>
|
|
246
248
|
)
|
|
247
249
|
})()}
|
|
@@ -352,6 +354,7 @@ export function ChatroomMessageBubble({ message, agents, onToggleReaction, onRep
|
|
|
352
354
|
}
|
|
353
355
|
return (
|
|
354
356
|
<a href={src} download target="_blank" rel="noopener noreferrer" className="block my-2">
|
|
357
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
355
358
|
<img src={src} alt={alt || 'Image'} loading="lazy" className="max-w-full max-h-[400px] rounded-[8px] border border-white/[0.06]" onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }} />
|
|
356
359
|
</a>
|
|
357
360
|
)
|
|
@@ -476,7 +479,7 @@ export function ChatroomMessageBubble({ message, agents, onToggleReaction, onRep
|
|
|
476
479
|
</button>
|
|
477
480
|
)}
|
|
478
481
|
{onMuteAgent && onUnmuteAgent && (() => {
|
|
479
|
-
const muted = isAgentMutedInChatroom(chatroom, message.senderId)
|
|
482
|
+
const muted = isAgentMutedInChatroom(chatroom, message.senderId, now)
|
|
480
483
|
return muted ? (
|
|
481
484
|
<button
|
|
482
485
|
onClick={() => {
|