@swarmclawai/swarmclaw 0.8.4 → 0.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/bin/swarmclaw.js +5 -1
- package/bin/worker-cmd.js +73 -0
- package/package.json +2 -1
- package/src/app/api/agents/[id]/route.ts +17 -7
- package/src/app/api/agents/route.ts +21 -8
- package/src/app/api/approvals/route.test.ts +6 -6
- package/src/app/api/approvals/route.ts +2 -1
- package/src/app/api/auth/route.ts +2 -3
- package/src/app/api/chatrooms/[id]/chat/route.test.ts +299 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +3 -2
- package/src/app/api/chatrooms/[id]/route.ts +7 -6
- package/src/app/api/chats/[id]/chat/route.test.ts +496 -0
- package/src/app/api/chats/[id]/chat/route.ts +7 -3
- package/src/app/api/chats/[id]/clear/route.ts +9 -9
- package/src/app/api/chats/[id]/devserver/route.ts +2 -1
- package/src/app/api/chats/[id]/edit-resend/route.ts +3 -4
- package/src/app/api/chats/[id]/fork/route.ts +3 -5
- package/src/app/api/chats/[id]/restore/route.ts +6 -7
- package/src/app/api/chats/[id]/retry/route.ts +3 -4
- package/src/app/api/chats/[id]/route.ts +61 -62
- package/src/app/api/chats/route.ts +7 -1
- package/src/app/api/connectors/[id]/route.ts +7 -8
- package/src/app/api/connectors/route.ts +5 -4
- package/src/app/api/eval/run/route.ts +2 -1
- package/src/app/api/eval/suite/route.ts +2 -1
- package/src/app/api/external-agents/route.test.ts +1 -1
- package/src/app/api/external-agents/route.ts +2 -2
- package/src/app/api/files/serve/route.ts +1 -1
- package/src/app/api/gateways/[id]/route.ts +7 -5
- package/src/app/api/gateways/route.ts +1 -1
- package/src/app/api/knowledge/upload/route.ts +1 -1
- package/src/app/api/logs/route.ts +5 -7
- package/src/app/api/memory-images/[filename]/route.ts +2 -3
- package/src/app/api/openclaw/agent-files/route.ts +4 -3
- package/src/app/api/openclaw/approvals/route.ts +3 -4
- package/src/app/api/openclaw/config-sync/route.ts +3 -2
- package/src/app/api/openclaw/cron/route.ts +3 -2
- package/src/app/api/openclaw/dotenv-keys/route.ts +2 -1
- package/src/app/api/openclaw/exec-config/route.ts +3 -2
- package/src/app/api/openclaw/gateway/route.ts +5 -4
- package/src/app/api/openclaw/history/route.ts +3 -2
- package/src/app/api/openclaw/media/route.ts +2 -1
- package/src/app/api/openclaw/permissions/route.ts +3 -2
- package/src/app/api/openclaw/sandbox-env/route.ts +3 -2
- package/src/app/api/openclaw/skills/install/route.ts +2 -1
- package/src/app/api/openclaw/skills/remove/route.ts +2 -1
- package/src/app/api/openclaw/skills/route.ts +3 -2
- package/src/app/api/orchestrator/run/route.ts +5 -14
- package/src/app/api/perf/route.ts +43 -0
- package/src/app/api/plugins/dependencies/route.ts +2 -1
- package/src/app/api/plugins/install/route.ts +2 -1
- package/src/app/api/plugins/marketplace/route.ts +3 -2
- package/src/app/api/plugins/settings/route.ts +2 -1
- package/src/app/api/preview-server/route.ts +11 -10
- package/src/app/api/projects/[id]/route.ts +1 -1
- package/src/app/api/schedules/[id]/route.test.ts +128 -0
- package/src/app/api/schedules/[id]/route.ts +43 -43
- package/src/app/api/schedules/[id]/run/route.ts +11 -62
- package/src/app/api/schedules/route.ts +21 -87
- package/src/app/api/settings/route.ts +2 -0
- package/src/app/api/setup/doctor/route.ts +9 -8
- package/src/app/api/tasks/[id]/approve/route.ts +33 -30
- package/src/app/api/tasks/[id]/route.ts +12 -35
- package/src/app/api/tasks/import/github/route.ts +2 -1
- package/src/app/api/tasks/route.ts +79 -91
- package/src/app/api/wallets/[id]/approve/route.ts +2 -1
- package/src/app/api/wallets/[id]/route.ts +13 -19
- package/src/app/api/wallets/[id]/send/route.ts +2 -1
- package/src/app/api/wallets/route.ts +2 -1
- package/src/app/api/webhooks/[id]/route.ts +2 -1
- package/src/app/api/webhooks/route.test.ts +3 -1
- package/src/app/page.tsx +23 -331
- package/src/cli/index.js +19 -0
- package/src/cli/index.ts +38 -7
- package/src/cli/spec.js +9 -0
- package/src/components/activity/activity-feed.tsx +7 -4
- package/src/components/agents/agent-card.tsx +32 -6
- package/src/components/agents/agent-chat-list.tsx +55 -22
- package/src/components/agents/agent-files-editor.tsx +3 -2
- package/src/components/agents/agent-sheet.tsx +123 -22
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/agents/openclaw-skills-panel.tsx +2 -1
- package/src/components/agents/trash-list.tsx +1 -1
- package/src/components/auth/access-key-gate.tsx +8 -2
- package/src/components/auth/setup-wizard.tsx +10 -9
- package/src/components/auth/user-picker.tsx +3 -2
- package/src/components/chat/chat-area.tsx +20 -1
- package/src/components/chat/chat-card.tsx +18 -3
- package/src/components/chat/chat-header.tsx +24 -4
- package/src/components/chat/chat-list.tsx +2 -11
- package/src/components/chat/heartbeat-history-panel.tsx +2 -1
- package/src/components/chat/message-bubble.tsx +45 -6
- package/src/components/chat/message-list.tsx +280 -145
- package/src/components/chat/streaming-bubble.tsx +217 -60
- package/src/components/chat/swarm-panel.test.ts +274 -0
- package/src/components/chat/swarm-panel.tsx +410 -0
- package/src/components/chat/swarm-status-card.tsx +346 -0
- package/src/components/chat/tool-call-bubble.tsx +48 -23
- package/src/components/chatrooms/chatroom-list.tsx +8 -5
- package/src/components/chatrooms/chatroom-message.tsx +10 -7
- package/src/components/chatrooms/chatroom-view.tsx +12 -9
- package/src/components/connectors/connector-health.tsx +6 -4
- package/src/components/connectors/connector-list.tsx +16 -11
- package/src/components/connectors/connector-sheet.tsx +12 -6
- package/src/components/home/home-view.tsx +38 -24
- package/src/components/input/chat-input.tsx +10 -1
- package/src/components/layout/app-layout.tsx +2 -38
- package/src/components/layout/sheet-layer.tsx +50 -0
- package/src/components/mcp-servers/mcp-server-list.tsx +37 -5
- package/src/components/mcp-servers/mcp-server-sheet.tsx +12 -2
- package/src/components/plugins/plugin-list.tsx +8 -4
- package/src/components/plugins/plugin-sheet.tsx +2 -1
- package/src/components/providers/provider-list.tsx +3 -2
- package/src/components/providers/provider-sheet.tsx +2 -1
- package/src/components/runs/run-list.tsx +11 -7
- package/src/components/schedules/schedule-card.tsx +5 -3
- package/src/components/shared/agent-switch-dialog.tsx +1 -1
- package/src/components/shared/attachment-chip.tsx +19 -3
- package/src/components/shared/notification-center.tsx +6 -3
- package/src/components/shared/settings/plugin-manager.tsx +3 -2
- package/src/components/shared/settings/section-embedding.tsx +2 -1
- package/src/components/shared/settings/section-orchestrator.tsx +2 -1
- package/src/components/shared/settings/section-user-preferences.tsx +107 -0
- package/src/components/shared/settings/settings-page.tsx +13 -9
- package/src/components/skills/clawhub-browser.tsx +15 -4
- package/src/components/skills/skill-list.tsx +15 -4
- package/src/components/tasks/approvals-panel.tsx +2 -1
- package/src/components/tasks/task-board.tsx +35 -37
- package/src/components/tasks/task-sheet.tsx +4 -3
- package/src/components/ui/full-screen-loader.tsx +164 -0
- package/src/components/wallets/wallet-approval-dialog.tsx +2 -1
- package/src/components/wallets/wallet-panel.tsx +6 -5
- package/src/components/wallets/wallet-section.tsx +3 -2
- package/src/components/webhooks/webhook-list.tsx +4 -5
- package/src/components/webhooks/webhook-sheet.tsx +6 -6
- package/src/hooks/use-app-bootstrap.ts +202 -0
- package/src/hooks/use-mounted-ref.ts +14 -0
- package/src/hooks/use-now.ts +31 -0
- package/src/hooks/use-openclaw-gateway.ts +2 -1
- package/src/instrumentation.ts +20 -8
- package/src/lib/agent-default-tools.test.ts +52 -0
- package/src/lib/agent-default-tools.ts +40 -0
- package/src/lib/api-client.test.ts +21 -0
- package/src/lib/api-client.ts +6 -11
- package/src/lib/canvas-content.test.ts +360 -0
- package/src/lib/chat-streaming-state.test.ts +49 -2
- package/src/lib/chat-streaming-state.ts +26 -10
- package/src/lib/fetch-timeout.test.ts +54 -0
- package/src/lib/fetch-timeout.ts +60 -3
- package/src/lib/live-tool-events.test.ts +77 -0
- package/src/lib/live-tool-events.ts +73 -0
- package/src/lib/local-observability.test.ts +2 -2
- package/src/lib/openclaw-endpoint.test.ts +1 -1
- package/src/lib/providers/anthropic.ts +12 -16
- package/src/lib/providers/index.ts +4 -2
- package/src/lib/providers/ollama.ts +9 -6
- package/src/lib/providers/openai.ts +11 -14
- package/src/lib/runtime-env.test.ts +8 -8
- package/src/lib/schedule-dedupe-advanced.test.ts +2 -2
- package/src/lib/schedule-dedupe.test.ts +1 -1
- package/src/lib/schedule-dedupe.ts +3 -2
- package/src/lib/server/agent-thread-session.test.ts +6 -6
- package/src/lib/server/agent-thread-session.ts +6 -9
- package/src/lib/server/alert-dispatch.ts +2 -1
- package/src/lib/server/api-routes.test.ts +6 -6
- package/src/lib/server/approval-connector-notify.test.ts +4 -4
- package/src/lib/server/approvals-auto-approve.test.ts +29 -29
- package/src/lib/server/approvals.test.ts +317 -0
- package/src/lib/server/approvals.ts +5 -4
- package/src/lib/server/autonomy-runtime.test.ts +11 -11
- package/src/lib/server/browser-state.ts +2 -2
- package/src/lib/server/capability-router.test.ts +1 -1
- package/src/lib/server/capability-router.ts +3 -2
- package/src/lib/server/chat-execution-advanced.test.ts +15 -2
- package/src/lib/server/chat-execution-connector-delivery.ts +67 -0
- package/src/lib/server/chat-execution-disabled.test.ts +3 -3
- package/src/lib/server/chat-execution-eval-history.test.ts +3 -3
- package/src/lib/server/chat-execution-heartbeat.test.ts +42 -1
- package/src/lib/server/chat-execution-session-sync.test.ts +119 -0
- package/src/lib/server/chat-execution-tool-events.ts +116 -0
- package/src/lib/server/chat-execution-utils.test.ts +479 -0
- package/src/lib/server/chat-execution-utils.ts +533 -0
- package/src/lib/server/chat-execution.ts +153 -748
- package/src/lib/server/chat-streaming-utils.ts +174 -0
- package/src/lib/server/chat-turn-tool-routing.ts +310 -0
- package/src/lib/server/chatroom-session-persistence.test.ts +2 -2
- package/src/lib/server/clawhub-client.ts +2 -1
- package/src/lib/server/collection-helpers.test.ts +92 -0
- package/src/lib/server/collection-helpers.ts +25 -3
- package/src/lib/server/connectors/access.ts +146 -0
- package/src/lib/server/connectors/bluebubbles.test.ts +1 -1
- package/src/lib/server/connectors/bluebubbles.ts +4 -4
- package/src/lib/server/connectors/commands.ts +367 -0
- package/src/lib/server/connectors/connector-routing.test.ts +4 -4
- package/src/lib/server/connectors/delivery.ts +142 -0
- package/src/lib/server/connectors/discord.ts +37 -40
- package/src/lib/server/connectors/email.ts +11 -10
- package/src/lib/server/connectors/googlechat.ts +4 -4
- package/src/lib/server/connectors/inbound-audio-transcription.ts +2 -1
- package/src/lib/server/connectors/ingress-delivery.ts +23 -0
- package/src/lib/server/connectors/manager-roundtrip.test.ts +300 -0
- package/src/lib/server/connectors/manager.test.ts +352 -77
- package/src/lib/server/connectors/manager.ts +134 -673
- package/src/lib/server/connectors/matrix.ts +4 -4
- package/src/lib/server/connectors/message-sentinel.ts +7 -0
- package/src/lib/server/connectors/openclaw.test.ts +1 -1
- package/src/lib/server/connectors/openclaw.ts +8 -10
- package/src/lib/server/connectors/outbox.test.ts +192 -0
- package/src/lib/server/connectors/outbox.ts +369 -0
- package/src/lib/server/connectors/pairing.test.ts +18 -1
- package/src/lib/server/connectors/pairing.ts +49 -4
- package/src/lib/server/connectors/policy.ts +9 -3
- package/src/lib/server/connectors/reconnect-state.ts +71 -0
- package/src/lib/server/connectors/response-media.ts +256 -0
- package/src/lib/server/connectors/runtime-state.ts +67 -0
- package/src/lib/server/connectors/session.test.ts +357 -0
- package/src/lib/server/connectors/session.ts +422 -0
- package/src/lib/server/connectors/signal.ts +7 -7
- package/src/lib/server/connectors/slack.ts +43 -43
- package/src/lib/server/connectors/teams.ts +4 -4
- package/src/lib/server/connectors/telegram.ts +37 -43
- package/src/lib/server/connectors/types.ts +31 -1
- package/src/lib/server/connectors/whatsapp.test.ts +108 -0
- package/src/lib/server/connectors/whatsapp.ts +106 -34
- package/src/lib/server/context-manager.test.ts +409 -0
- package/src/lib/server/cost.test.ts +1 -1
- package/src/lib/server/daemon-policy.ts +78 -0
- package/src/lib/server/daemon-state-connectors.test.ts +167 -0
- package/src/lib/server/daemon-state.test.ts +283 -55
- package/src/lib/server/daemon-state.ts +106 -109
- package/src/lib/server/data-dir.test.ts +5 -5
- package/src/lib/server/data-dir.ts +4 -0
- package/src/lib/server/delegation-jobs-advanced.test.ts +1 -1
- package/src/lib/server/delegation-jobs.test.ts +87 -0
- package/src/lib/server/delegation-jobs.ts +42 -48
- package/src/lib/server/devserver-launch.ts +1 -1
- package/src/lib/server/document-utils.ts +7 -9
- package/src/lib/server/elevenlabs.ts +2 -1
- package/src/lib/server/embeddings.test.ts +105 -0
- package/src/lib/server/ethereum.ts +3 -2
- package/src/lib/server/eval/agent-regression.ts +3 -2
- package/src/lib/server/eval/runner.ts +2 -1
- package/src/lib/server/eval/scorer.ts +2 -1
- package/src/lib/server/evm-swap.ts +2 -1
- package/src/lib/server/gateway/protocol.test.ts +1 -1
- package/src/lib/server/guardian.ts +2 -1
- package/src/lib/server/heartbeat-blocked-suppression.test.ts +151 -0
- package/src/lib/server/heartbeat-service-timer.test.ts +6 -6
- package/src/lib/server/heartbeat-service.test.ts +406 -0
- package/src/lib/server/heartbeat-service.ts +54 -7
- package/src/lib/server/heartbeat-wake.test.ts +19 -0
- package/src/lib/server/heartbeat-wake.ts +17 -16
- package/src/lib/server/integrity-monitor.test.ts +149 -0
- package/src/lib/server/json-utils.ts +22 -0
- package/src/lib/server/knowledge-db.test.ts +13 -13
- package/src/lib/server/link-understanding.ts +2 -1
- package/src/lib/server/llm-response-cache.test.ts +1 -1
- package/src/lib/server/main-agent-loop-advanced.test.ts +65 -3
- package/src/lib/server/main-agent-loop.test.ts +6 -6
- package/src/lib/server/main-agent-loop.ts +21 -7
- package/src/lib/server/mcp-client.test.ts +1 -1
- package/src/lib/server/mcp-conformance.test.ts +1 -1
- package/src/lib/server/mcp-conformance.ts +3 -2
- package/src/lib/server/memory-consolidation.ts +2 -1
- package/src/lib/server/memory-db.test.ts +485 -0
- package/src/lib/server/memory-db.ts +39 -26
- package/src/lib/server/memory-graph.test.ts +2 -2
- package/src/lib/server/memory-policy.test.ts +7 -7
- package/src/lib/server/memory-retrieval.test.ts +1 -1
- package/src/lib/server/openclaw-config-sync.ts +2 -1
- package/src/lib/server/openclaw-deploy.test.ts +1 -1
- package/src/lib/server/openclaw-deploy.ts +8 -12
- package/src/lib/server/openclaw-exec-config.ts +2 -1
- package/src/lib/server/openclaw-gateway.ts +6 -7
- package/src/lib/server/openclaw-skills-normalize.ts +2 -1
- package/src/lib/server/openclaw-sync.ts +7 -5
- package/src/lib/server/orchestrator-lg-structure.test.ts +17 -0
- package/src/lib/server/orchestrator-lg.ts +199 -327
- package/src/lib/server/path-utils.ts +31 -0
- package/src/lib/server/perf.ts +161 -0
- package/src/lib/server/plugins-approval-guidance.ts +115 -0
- package/src/lib/server/plugins.test.ts +1 -1
- package/src/lib/server/plugins.ts +22 -132
- package/src/lib/server/process-manager.ts +5 -8
- package/src/lib/server/provider-health.test.ts +137 -0
- package/src/lib/server/provider-health.ts +3 -3
- package/src/lib/server/provider-model-discovery.ts +3 -12
- package/src/lib/server/queue-followups.test.ts +9 -9
- package/src/lib/server/queue-reconcile.test.ts +2 -2
- package/src/lib/server/queue-recovery.test.ts +269 -0
- package/src/lib/server/queue.test.ts +570 -0
- package/src/lib/server/queue.ts +62 -455
- package/src/lib/server/resolve-image.ts +30 -0
- package/src/lib/server/runtime-settings.test.ts +4 -4
- package/src/lib/server/runtime-storage-write-paths.test.ts +60 -0
- package/src/lib/server/schedule-normalization.test.ts +279 -0
- package/src/lib/server/schedule-service.ts +263 -0
- package/src/lib/server/scheduler.ts +17 -74
- package/src/lib/server/session-mailbox.test.ts +191 -0
- package/src/lib/server/session-run-manager.test.ts +640 -0
- package/src/lib/server/session-run-manager.ts +59 -15
- package/src/lib/server/session-tools/autonomy-tools.test.ts +20 -20
- package/src/lib/server/session-tools/calendar.ts +2 -1
- package/src/lib/server/session-tools/canvas.ts +2 -1
- package/src/lib/server/session-tools/chatroom.ts +2 -1
- package/src/lib/server/session-tools/connector.ts +26 -28
- package/src/lib/server/session-tools/context-mgmt.ts +3 -2
- package/src/lib/server/session-tools/crawl.ts +4 -3
- package/src/lib/server/session-tools/crud.ts +105 -324
- package/src/lib/server/session-tools/delegate-fallback.test.ts +9 -9
- package/src/lib/server/session-tools/delegate.ts +6 -8
- package/src/lib/server/session-tools/discovery-approvals.test.ts +15 -15
- package/src/lib/server/session-tools/discovery.ts +4 -3
- package/src/lib/server/session-tools/document.ts +2 -1
- package/src/lib/server/session-tools/email.ts +2 -1
- package/src/lib/server/session-tools/extract.ts +2 -1
- package/src/lib/server/session-tools/file.ts +4 -3
- package/src/lib/server/session-tools/http.ts +2 -1
- package/src/lib/server/session-tools/human-loop.ts +2 -1
- package/src/lib/server/session-tools/image-gen.ts +4 -3
- package/src/lib/server/session-tools/index.ts +26 -30
- package/src/lib/server/session-tools/mailbox.ts +2 -1
- package/src/lib/server/session-tools/manage-connectors.test.ts +4 -4
- package/src/lib/server/session-tools/manage-schedules.test.ts +12 -12
- package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +5 -5
- package/src/lib/server/session-tools/manage-tasks.test.ts +2 -2
- package/src/lib/server/session-tools/monitor.ts +2 -1
- package/src/lib/server/session-tools/platform.ts +2 -1
- package/src/lib/server/session-tools/plugin-creator.ts +2 -1
- package/src/lib/server/session-tools/replicate.ts +3 -2
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +6 -6
- package/src/lib/server/session-tools/shell.ts +4 -9
- package/src/lib/server/session-tools/subagent.ts +322 -170
- package/src/lib/server/session-tools/table.ts +6 -5
- package/src/lib/server/session-tools/wallet-tool.test.ts +3 -3
- package/src/lib/server/session-tools/wallet.ts +7 -6
- package/src/lib/server/session-tools/web-browser-config.test.ts +1 -0
- package/src/lib/server/session-tools/web-utils.ts +317 -0
- package/src/lib/server/session-tools/web.ts +62 -328
- package/src/lib/server/skill-prompt-budget.test.ts +1 -1
- package/src/lib/server/skills-normalize.ts +2 -1
- package/src/lib/server/storage-item-access.test.ts +302 -0
- package/src/lib/server/storage.ts +366 -314
- package/src/lib/server/stream-agent-chat.test.ts +82 -3
- package/src/lib/server/stream-agent-chat.ts +146 -510
- package/src/lib/server/stream-continuation.ts +412 -0
- package/src/lib/server/subagent-lineage.test.ts +647 -0
- package/src/lib/server/subagent-lineage.ts +435 -0
- package/src/lib/server/subagent-runtime.test.ts +484 -0
- package/src/lib/server/subagent-runtime.ts +419 -0
- package/src/lib/server/subagent-swarm.test.ts +391 -0
- package/src/lib/server/subagent-swarm.ts +564 -0
- package/src/lib/server/system-events.ts +3 -3
- package/src/lib/server/task-followups.test.ts +491 -0
- package/src/lib/server/task-followups.ts +391 -0
- package/src/lib/server/task-lifecycle.test.ts +205 -0
- package/src/lib/server/task-lifecycle.ts +200 -0
- package/src/lib/server/task-quality-gate.test.ts +1 -1
- package/src/lib/server/task-resume.ts +208 -0
- package/src/lib/server/task-service.test.ts +108 -0
- package/src/lib/server/task-service.ts +264 -0
- package/src/lib/server/task-validation.test.ts +1 -1
- package/src/lib/server/test-utils/run-with-temp-data-dir.ts +42 -0
- package/src/lib/server/tool-capability-policy.test.ts +2 -2
- package/src/lib/server/tool-capability-policy.ts +3 -2
- package/src/lib/server/tool-planning.ts +2 -1
- package/src/lib/server/tool-retry.ts +2 -3
- package/src/lib/server/wake-dispatcher.test.ts +303 -0
- package/src/lib/server/wake-dispatcher.ts +318 -0
- package/src/lib/server/wake-mode.test.ts +161 -0
- package/src/lib/server/wake-mode.ts +174 -0
- package/src/lib/server/wallet-service.ts +8 -9
- package/src/lib/server/watch-jobs.ts +2 -1
- package/src/lib/server/workspace-context.ts +2 -2
- package/src/lib/shared-utils.test.ts +142 -0
- package/src/lib/shared-utils.ts +62 -0
- package/src/lib/tool-event-summary.ts +2 -1
- package/src/lib/view-routes.test.ts +100 -0
- package/src/lib/wallet.test.ts +322 -6
- package/src/proxy.test.ts +4 -4
- package/src/proxy.ts +2 -3
- package/src/stores/set-if-changed.ts +40 -0
- package/src/stores/slices/agent-slice.ts +111 -0
- package/src/stores/slices/auth-slice.ts +25 -0
- package/src/stores/slices/data-slice.ts +301 -0
- package/src/stores/slices/index.ts +7 -0
- package/src/stores/slices/session-slice.ts +112 -0
- package/src/stores/slices/task-slice.ts +63 -0
- package/src/stores/slices/ui-slice.ts +192 -0
- package/src/stores/use-app-store.ts +17 -822
- package/src/stores/use-approval-store.ts +2 -1
- package/src/stores/use-chat-store.ts +8 -1
- package/src/types/index.ts +10 -0
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { DEFAULT_HEARTBEAT_SHOW_ALERTS, DEFAULT_HEARTBEAT_SHOW_OK } from '@/lib/heartbeat-defaults'
|
|
4
|
-
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
|
4
|
+
import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
|
5
5
|
import type { Message } from '@/types'
|
|
6
6
|
import { useChatStore } from '@/stores/use-chat-store'
|
|
7
7
|
import { useAppStore } from '@/stores/use-app-store'
|
|
8
8
|
import { api } from '@/lib/api-client'
|
|
9
9
|
import { shouldHidePersistedStreamingAssistantMessage } from '@/lib/chat-streaming-state'
|
|
10
10
|
import { dedupeMessagesForDisplay } from '@/lib/chat-display'
|
|
11
|
+
import { errorMessage } from '@/lib/shared-utils'
|
|
11
12
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
12
13
|
import { MessageBubble } from './message-bubble'
|
|
13
14
|
import { StreamingBubble } from './streaming-bubble'
|
|
@@ -71,12 +72,63 @@ interface Props {
|
|
|
71
72
|
loading?: boolean
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
interface LiveStreamLaneProps {
|
|
76
|
+
streaming: boolean
|
|
77
|
+
hasVisiblePersistedStreamingMessage: boolean
|
|
78
|
+
assistantName?: string
|
|
79
|
+
agentAvatarSeed?: string
|
|
80
|
+
agentAvatarUrl?: string | null
|
|
81
|
+
agentName?: string
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const LiveStreamLane = memo(function LiveStreamLane({
|
|
85
|
+
streaming,
|
|
86
|
+
hasVisiblePersistedStreamingMessage,
|
|
87
|
+
assistantName,
|
|
88
|
+
agentAvatarSeed,
|
|
89
|
+
agentAvatarUrl,
|
|
90
|
+
agentName,
|
|
91
|
+
}: LiveStreamLaneProps) {
|
|
92
|
+
const displayText = useChatStore((s) => s.displayText)
|
|
93
|
+
const hasToolEvents = useChatStore((s) => s.toolEvents.length > 0)
|
|
94
|
+
|
|
95
|
+
if (!streaming) return null
|
|
96
|
+
|
|
97
|
+
if (!displayText && !hasToolEvents) {
|
|
98
|
+
if (hasVisiblePersistedStreamingMessage) return null
|
|
99
|
+
return (
|
|
100
|
+
<ThinkingIndicator
|
|
101
|
+
assistantName={assistantName}
|
|
102
|
+
agentAvatarSeed={agentAvatarSeed}
|
|
103
|
+
agentAvatarUrl={agentAvatarUrl}
|
|
104
|
+
agentName={agentName}
|
|
105
|
+
/>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<StreamingBubble
|
|
111
|
+
text={displayText}
|
|
112
|
+
assistantName={assistantName}
|
|
113
|
+
agentAvatarSeed={agentAvatarSeed}
|
|
114
|
+
agentAvatarUrl={agentAvatarUrl}
|
|
115
|
+
agentName={agentName}
|
|
116
|
+
/>
|
|
117
|
+
)
|
|
118
|
+
})
|
|
119
|
+
|
|
74
120
|
export function MessageList({ messages, streaming, connectorFilter = null, loading = false }: Props) {
|
|
75
121
|
const scrollRef = useRef<HTMLDivElement>(null)
|
|
76
122
|
const [showScrollToBottom, setShowScrollToBottom] = useState(false)
|
|
123
|
+
const settledCountRef = useRef(0)
|
|
77
124
|
const snapUntilRef = useRef(0)
|
|
78
125
|
const prevSessionIdRef = useRef<string | null>(null)
|
|
79
|
-
const
|
|
126
|
+
const hasLiveText = useChatStore((s) => s.displayText.trim().length > 0)
|
|
127
|
+
const hasLiveArtifacts = useChatStore((s) => (
|
|
128
|
+
s.displayText.trim().length > 0
|
|
129
|
+
|| s.toolEvents.length > 0
|
|
130
|
+
|| s.thinkingText.trim().length > 0
|
|
131
|
+
))
|
|
80
132
|
const setMessages = useChatStore((s) => s.setMessages)
|
|
81
133
|
const retryLastMessage = useChatStore((s) => s.retryLastMessage)
|
|
82
134
|
const editAndResend = useChatStore((s) => s.editAndResend)
|
|
@@ -152,33 +204,41 @@ export function MessageList({ messages, streaming, connectorFilter = null, loadi
|
|
|
152
204
|
|
|
153
205
|
// Connector filtering is handled via connectorFilter prop from chat-area
|
|
154
206
|
|
|
207
|
+
// Use refs for callbacks so transcriptNodes memo doesn't bust on every messages change
|
|
208
|
+
const messagesCallbackRef = useRef(messages)
|
|
209
|
+
messagesCallbackRef.current = messages
|
|
210
|
+
const sessionIdRef = useRef(sessionId)
|
|
211
|
+
sessionIdRef.current = sessionId
|
|
212
|
+
|
|
155
213
|
const toggleBookmark = useCallback(async (index: number) => {
|
|
156
|
-
|
|
157
|
-
const
|
|
214
|
+
const sid = sessionIdRef.current
|
|
215
|
+
const msgs = messagesCallbackRef.current
|
|
216
|
+
if (!sid) return
|
|
217
|
+
const msg = msgs[index]
|
|
158
218
|
if (!msg) return
|
|
159
219
|
const next = !msg.bookmarked
|
|
160
220
|
try {
|
|
161
|
-
await api('PUT', `/chats/${
|
|
162
|
-
const updated = [...
|
|
221
|
+
await api('PUT', `/chats/${sid}/messages`, { messageIndex: index, bookmarked: next })
|
|
222
|
+
const updated = [...msgs]
|
|
163
223
|
updated[index] = { ...updated[index], bookmarked: next }
|
|
164
224
|
setMessages(updated)
|
|
165
225
|
} catch (err: unknown) {
|
|
166
|
-
console.error('Failed to toggle bookmark:',
|
|
226
|
+
console.error('Failed to toggle bookmark:', errorMessage(err))
|
|
167
227
|
}
|
|
168
228
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
169
|
-
}, [
|
|
229
|
+
}, [])
|
|
170
230
|
|
|
171
231
|
const handleEditResend = useCallback(async (index: number, newText: string) => {
|
|
172
|
-
if (!
|
|
232
|
+
if (!sessionIdRef.current || !editAndResend) return
|
|
173
233
|
await editAndResend(index, newText)
|
|
174
234
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
175
|
-
}, [
|
|
235
|
+
}, [])
|
|
176
236
|
|
|
177
237
|
const handleFork = useCallback(async (index: number) => {
|
|
178
|
-
if (!
|
|
179
|
-
await forkSession(
|
|
238
|
+
if (!sessionIdRef.current || !forkSession) return
|
|
239
|
+
await forkSession(sessionIdRef.current, index)
|
|
180
240
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
181
|
-
}, [
|
|
241
|
+
}, [])
|
|
182
242
|
|
|
183
243
|
// In-thread search
|
|
184
244
|
const [searchOpen, setSearchOpen] = useState(false)
|
|
@@ -195,36 +255,43 @@ export function MessageList({ messages, streaming, connectorFilter = null, loadi
|
|
|
195
255
|
const isHeartbeatOk = (msg: Message) =>
|
|
196
256
|
msg.suppressed === true || (msg.kind === 'heartbeat' && (/^\s*HEARTBEAT_OK\b/i.test(msg.text || '') || /^\s*NO_MESSAGE\b/i.test(msg.text || '')))
|
|
197
257
|
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
258
|
+
const dedupedDisplayedMessages = useMemo(() => {
|
|
259
|
+
const displayedMessages: Message[] = []
|
|
260
|
+
for (const msg of messages) {
|
|
261
|
+
if (shouldHidePersistedStreamingAssistantMessage(msg, { localStreaming: streaming, hasLiveArtifacts })) continue
|
|
262
|
+
const isHeartbeat = isHeartbeatMessage(msg)
|
|
202
263
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
264
|
+
if (isHeartbeat) {
|
|
265
|
+
if (!showAlerts) continue
|
|
266
|
+
if (!showOk && isHeartbeatOk(msg)) continue
|
|
267
|
+
}
|
|
208
268
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
269
|
+
const last = displayedMessages[displayedMessages.length - 1]
|
|
270
|
+
const lastIsHeartbeat = !!last && isHeartbeatMessage(last)
|
|
271
|
+
if (isHeartbeat && lastIsHeartbeat) {
|
|
272
|
+
displayedMessages[displayedMessages.length - 1] = msg
|
|
273
|
+
} else {
|
|
274
|
+
displayedMessages.push(msg)
|
|
275
|
+
}
|
|
215
276
|
}
|
|
216
|
-
}
|
|
217
277
|
|
|
218
|
-
|
|
278
|
+
return dedupeMessagesForDisplay(displayedMessages)
|
|
279
|
+
}, [hasLiveArtifacts, messages, showAlerts, showOk, streaming])
|
|
219
280
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
281
|
+
const filteredMessages = useMemo(() => {
|
|
282
|
+
let nextMessages = bookmarkFilter
|
|
283
|
+
? dedupedDisplayedMessages.filter((msg) => msg.bookmarked)
|
|
284
|
+
: dedupedDisplayedMessages
|
|
285
|
+
if (connectorFilter) {
|
|
286
|
+
nextMessages = nextMessages.filter((msg) => msg.source?.connectorId === connectorFilter)
|
|
287
|
+
}
|
|
288
|
+
return nextMessages
|
|
289
|
+
}, [bookmarkFilter, connectorFilter, dedupedDisplayedMessages])
|
|
290
|
+
|
|
291
|
+
const hasVisiblePersistedStreamingMessage = useMemo(
|
|
292
|
+
() => filteredMessages.some((msg) => msg.role === 'assistant' && msg.streaming === true),
|
|
293
|
+
[filteredMessages],
|
|
294
|
+
)
|
|
228
295
|
|
|
229
296
|
// Search matches
|
|
230
297
|
const searchMatches = useMemo(() => {
|
|
@@ -234,6 +301,158 @@ export function MessageList({ messages, streaming, connectorFilter = null, loadi
|
|
|
234
301
|
.map((msg, i) => ({ msg, i }))
|
|
235
302
|
.filter(({ msg }) => msg.text.toLowerCase().includes(normalizedQuery))
|
|
236
303
|
}, [filteredMessages, searchQuery])
|
|
304
|
+
const searchMatchSet = useMemo(() => new Set(searchMatches.map((match) => match.i)), [searchMatches])
|
|
305
|
+
const currentSearchMatchIndex = searchQuery ? (searchMatches[searchIdx]?.i ?? null) : null
|
|
306
|
+
const originalIndexMap = useMemo(() => {
|
|
307
|
+
const indexMap = new Map<Message, number>()
|
|
308
|
+
messages.forEach((msg, index) => {
|
|
309
|
+
indexMap.set(msg, index)
|
|
310
|
+
})
|
|
311
|
+
return indexMap
|
|
312
|
+
}, [messages])
|
|
313
|
+
|
|
314
|
+
const handleDeleteMessage = useCallback(async (messageIndex: number) => {
|
|
315
|
+
const sid = sessionIdRef.current
|
|
316
|
+
const msgs = messagesCallbackRef.current
|
|
317
|
+
if (!sid || messageIndex < 0) return
|
|
318
|
+
try {
|
|
319
|
+
await api('DELETE', `/chats/${sid}/messages`, { messageIndex })
|
|
320
|
+
setMessages(msgs.filter((_: Message, idx: number) => idx !== messageIndex))
|
|
321
|
+
} catch {
|
|
322
|
+
// best-effort
|
|
323
|
+
}
|
|
324
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
325
|
+
}, [])
|
|
326
|
+
|
|
327
|
+
// Snapshot the settled count at memo time so it's captured in the closure.
|
|
328
|
+
// Messages up to this count appear instantly; only new ones get entrance animations.
|
|
329
|
+
const settledSnapshot = settledCountRef.current
|
|
330
|
+
|
|
331
|
+
const transcriptNodes = useMemo(() => {
|
|
332
|
+
let lastAssistantIndex = -1
|
|
333
|
+
if (!streaming) {
|
|
334
|
+
for (let i = filteredMessages.length - 1; i >= 0; i--) {
|
|
335
|
+
if (filteredMessages[i].role === 'assistant') {
|
|
336
|
+
lastAssistantIndex = i
|
|
337
|
+
break
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return filteredMessages.map((msg, i) => {
|
|
343
|
+
if (msg.kind === 'context-clear') {
|
|
344
|
+
const originalIndex = originalIndexMap.get(msg) ?? -1
|
|
345
|
+
return (
|
|
346
|
+
<div key={`ctx-clear-${msg.time}-${i}`} className="group/ctx flex items-center gap-4 py-3">
|
|
347
|
+
<div className="flex-1 h-px bg-amber-400/20" />
|
|
348
|
+
<span className="flex items-center gap-1.5 text-[10px] font-600 text-amber-400/60 uppercase tracking-[0.1em]">
|
|
349
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="shrink-0">
|
|
350
|
+
<line x1="2" y1="12" x2="22" y2="12" />
|
|
351
|
+
<polyline points="8 8 4 12 8 16" />
|
|
352
|
+
<polyline points="16 8 20 12 16 16" />
|
|
353
|
+
</svg>
|
|
354
|
+
New context
|
|
355
|
+
{msg.time ? ` · ${new Date(msg.time).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}` : ''}
|
|
356
|
+
</span>
|
|
357
|
+
{sessionId && originalIndex >= 0 && (
|
|
358
|
+
<button
|
|
359
|
+
type="button"
|
|
360
|
+
onClick={() => void handleDeleteMessage(originalIndex)}
|
|
361
|
+
className="opacity-0 group-hover/ctx:opacity-100 text-[10px] font-600 text-amber-400/60 hover:text-amber-400 bg-transparent border-none cursor-pointer transition-all px-1.5 py-0.5 rounded-[4px] hover:bg-amber-400/10"
|
|
362
|
+
title="Undo — restore full context"
|
|
363
|
+
>
|
|
364
|
+
Undo
|
|
365
|
+
</button>
|
|
366
|
+
)}
|
|
367
|
+
<div className="flex-1 h-px bg-amber-400/20" />
|
|
368
|
+
</div>
|
|
369
|
+
)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const originalIndex = originalIndexMap.get(msg) ?? -1
|
|
373
|
+
const isLastAssistant = i === lastAssistantIndex
|
|
374
|
+
const isSearchMatch = !!searchQuery && searchMatchSet.has(i)
|
|
375
|
+
const isCurrentMatch = currentSearchMatchIndex === i
|
|
376
|
+
const prevMsg = i > 0 ? filteredMessages[i - 1] : null
|
|
377
|
+
const showDateSep = msg.time && (!prevMsg?.time || new Date(msg.time).toDateString() !== new Date(prevMsg.time).toDateString())
|
|
378
|
+
|
|
379
|
+
let momentOverlay: React.ReactNode = null
|
|
380
|
+
if (isLastAssistant && currentMoment && !streaming) {
|
|
381
|
+
if (currentMoment.kind === 'heartbeat') {
|
|
382
|
+
momentOverlay = <HeartbeatMoment onDismiss={() => setCurrentMoment(null)} />
|
|
383
|
+
} else {
|
|
384
|
+
momentOverlay = (
|
|
385
|
+
<ActivityMoment
|
|
386
|
+
key={currentMoment.key}
|
|
387
|
+
toolName={currentMoment.name}
|
|
388
|
+
toolInput={currentMoment.input}
|
|
389
|
+
onDismiss={() => setCurrentMoment(null)}
|
|
390
|
+
/>
|
|
391
|
+
)
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Only animate genuinely new messages (arrived after the batch load).
|
|
396
|
+
// Settled messages (loaded on session switch) appear instantly.
|
|
397
|
+
const isSettled = i < settledSnapshot
|
|
398
|
+
const animStyle = isSettled
|
|
399
|
+
? undefined
|
|
400
|
+
: {
|
|
401
|
+
animation: `${msg.role === 'user' ? 'msg-in-right' : 'msg-in-left'} 0.4s var(--ease-spring) both`,
|
|
402
|
+
animationDelay: `${Math.min((i - settledSnapshot) * 0.05, 0.4)}s`,
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return (
|
|
406
|
+
<div
|
|
407
|
+
key={`${sessionId}-${msg.role}-${originalIndex >= 0 ? originalIndex : i}`}
|
|
408
|
+
data-message-index={i}
|
|
409
|
+
style={animStyle}
|
|
410
|
+
>
|
|
411
|
+
{showDateSep && (
|
|
412
|
+
<div className="flex items-center gap-4 py-2 mb-2">
|
|
413
|
+
<div className="flex-1 h-px bg-white/[0.06]" />
|
|
414
|
+
<span className="text-[10px] font-600 text-text-3/50 uppercase tracking-[0.1em]">
|
|
415
|
+
{dateSeparator(msg.time)}
|
|
416
|
+
</span>
|
|
417
|
+
<div className="flex-1 h-px bg-white/[0.06]" />
|
|
418
|
+
</div>
|
|
419
|
+
)}
|
|
420
|
+
<div className={isCurrentMatch ? 'ring-1 ring-amber-400/50 rounded-[16px] bg-amber-400/[0.04]' : isSearchMatch ? 'bg-white/[0.02] rounded-[16px]' : ''}>
|
|
421
|
+
<MessageBubble
|
|
422
|
+
message={msg}
|
|
423
|
+
assistantName={assistantName}
|
|
424
|
+
agentAvatarSeed={agent?.avatarSeed}
|
|
425
|
+
agentAvatarUrl={agent?.avatarUrl}
|
|
426
|
+
agentName={agent?.name}
|
|
427
|
+
isLast={isLastAssistant}
|
|
428
|
+
onRetry={isLastAssistant ? retryLastMessage : undefined}
|
|
429
|
+
messageIndex={originalIndex >= 0 ? originalIndex : undefined}
|
|
430
|
+
onToggleBookmark={toggleBookmark}
|
|
431
|
+
onEditResend={handleEditResend}
|
|
432
|
+
onFork={handleFork}
|
|
433
|
+
momentOverlay={momentOverlay}
|
|
434
|
+
/>
|
|
435
|
+
</div>
|
|
436
|
+
</div>
|
|
437
|
+
)
|
|
438
|
+
})
|
|
439
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
440
|
+
}, [
|
|
441
|
+
agent?.avatarSeed,
|
|
442
|
+
agent?.avatarUrl,
|
|
443
|
+
agent?.name,
|
|
444
|
+
assistantName,
|
|
445
|
+
currentMoment,
|
|
446
|
+
currentSearchMatchIndex,
|
|
447
|
+
filteredMessages,
|
|
448
|
+
originalIndexMap,
|
|
449
|
+
retryLastMessage,
|
|
450
|
+
searchMatchSet,
|
|
451
|
+
searchQuery,
|
|
452
|
+
sessionId,
|
|
453
|
+
settledSnapshot,
|
|
454
|
+
streaming,
|
|
455
|
+
])
|
|
237
456
|
|
|
238
457
|
// Track whether user is at/near bottom so we know whether to auto-scroll on new content
|
|
239
458
|
const wasAtBottomRef = useRef(true)
|
|
@@ -275,8 +494,15 @@ export function MessageList({ messages, streaming, connectorFilter = null, loadi
|
|
|
275
494
|
prevSessionIdRef.current = sessionId
|
|
276
495
|
wasAtBottomRef.current = true
|
|
277
496
|
snapUntilRef.current = Date.now() + 2000
|
|
497
|
+
// Mark all messages as settled — new messages loaded for this session
|
|
498
|
+
// will also be settled (see below).
|
|
499
|
+
settledCountRef.current = 0
|
|
500
|
+
} else if (Date.now() < snapUntilRef.current) {
|
|
501
|
+
// Still in the snap window (messages just loaded for this session).
|
|
502
|
+
// Treat the new batch as settled so they appear without stagger.
|
|
503
|
+
settledCountRef.current = messages.length
|
|
278
504
|
}
|
|
279
|
-
}, [sessionId])
|
|
505
|
+
}, [messages.length, sessionId])
|
|
280
506
|
|
|
281
507
|
// Position scroll before paint — no setState here to avoid cascading renders.
|
|
282
508
|
// The onScroll handler and the state-update effect below handle UI state.
|
|
@@ -290,14 +516,14 @@ export function MessageList({ messages, streaming, connectorFilter = null, loadi
|
|
|
290
516
|
el.scrollTop = el.scrollHeight
|
|
291
517
|
wasAtBottomRef.current = true
|
|
292
518
|
}
|
|
293
|
-
}, [messages.length
|
|
519
|
+
}, [hasLiveText, messages.length])
|
|
294
520
|
|
|
295
521
|
// Update scroll-related UI state after render (separate from layoutEffect to avoid cascading)
|
|
296
522
|
useEffect(() => {
|
|
297
523
|
const el = scrollRef.current
|
|
298
524
|
if (!el || messages.length === 0) return
|
|
299
525
|
updateScrollState()
|
|
300
|
-
}, [messages.length,
|
|
526
|
+
}, [hasLiveText, messages.length, updateScrollState])
|
|
301
527
|
|
|
302
528
|
// Re-snap when content resizes during snap window (lazy images increasing scrollHeight)
|
|
303
529
|
useEffect(() => {
|
|
@@ -379,7 +605,7 @@ export function MessageList({ messages, streaming, connectorFilter = null, loadi
|
|
|
379
605
|
}, [searchOpen])
|
|
380
606
|
|
|
381
607
|
return (
|
|
382
|
-
<div className="relative flex-1 min-h-0 min-w-0 flex flex-col overflow-hidden">
|
|
608
|
+
<div className="relative flex-1 min-h-0 min-w-0 flex flex-col overflow-hidden" data-testid="message-list">
|
|
383
609
|
<div className="shrink-0 px-4 md:px-12 lg:px-16 pt-3">
|
|
384
610
|
<div className="flex flex-wrap items-center gap-2 rounded-[14px] border border-white/[0.06] bg-surface/55 px-3 py-2 backdrop-blur-sm">
|
|
385
611
|
<button
|
|
@@ -519,6 +745,9 @@ export function MessageList({ messages, streaming, connectorFilter = null, loadi
|
|
|
519
745
|
<div
|
|
520
746
|
ref={scrollRef}
|
|
521
747
|
onScroll={updateScrollState}
|
|
748
|
+
role="log"
|
|
749
|
+
aria-label="Conversation transcript"
|
|
750
|
+
data-testid="chat-thread"
|
|
522
751
|
className="flex-1 min-h-0 overflow-y-auto overflow-x-hidden px-4 md:px-12 lg:px-16 pt-4 pb-[120px] md:pb-10 fade-up"
|
|
523
752
|
>
|
|
524
753
|
<div className="flex flex-col gap-6 relative">
|
|
@@ -603,110 +832,16 @@ export function MessageList({ messages, streaming, connectorFilter = null, loadi
|
|
|
603
832
|
</div>
|
|
604
833
|
)
|
|
605
834
|
)}
|
|
606
|
-
{
|
|
607
|
-
// Context-clear divider — render a visual separator instead of a bubble
|
|
608
|
-
if (msg.kind === 'context-clear') {
|
|
609
|
-
const originalIndex = messages.indexOf(msg)
|
|
610
|
-
return (
|
|
611
|
-
<div key={`ctx-clear-${msg.time}-${i}`} className="group/ctx flex items-center gap-4 py-3">
|
|
612
|
-
<div className="flex-1 h-px bg-amber-400/20" />
|
|
613
|
-
<span className="flex items-center gap-1.5 text-[10px] font-600 text-amber-400/60 uppercase tracking-[0.1em]">
|
|
614
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="shrink-0">
|
|
615
|
-
<line x1="2" y1="12" x2="22" y2="12" />
|
|
616
|
-
<polyline points="8 8 4 12 8 16" />
|
|
617
|
-
<polyline points="16 8 20 12 16 16" />
|
|
618
|
-
</svg>
|
|
619
|
-
New context
|
|
620
|
-
{msg.time ? ` · ${new Date(msg.time).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}` : ''}
|
|
621
|
-
</span>
|
|
622
|
-
{sessionId && originalIndex >= 0 && (
|
|
623
|
-
<button
|
|
624
|
-
type="button"
|
|
625
|
-
onClick={async () => {
|
|
626
|
-
try {
|
|
627
|
-
await api('DELETE', `/chats/${sessionId}/messages`, { messageIndex: originalIndex })
|
|
628
|
-
setMessages(messages.filter((_: Message, idx: number) => idx !== originalIndex))
|
|
629
|
-
} catch { /* best-effort */ }
|
|
630
|
-
}}
|
|
631
|
-
className="opacity-0 group-hover/ctx:opacity-100 text-[10px] font-600 text-amber-400/60 hover:text-amber-400 bg-transparent border-none cursor-pointer transition-all px-1.5 py-0.5 rounded-[4px] hover:bg-amber-400/10"
|
|
632
|
-
title="Undo — restore full context"
|
|
633
|
-
>
|
|
634
|
-
Undo
|
|
635
|
-
</button>
|
|
636
|
-
)}
|
|
637
|
-
<div className="flex-1 h-px bg-amber-400/20" />
|
|
638
|
-
</div>
|
|
639
|
-
)
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
// Find original index in the full messages array for API calls
|
|
643
|
-
const originalIndex = messages.indexOf(msg)
|
|
644
|
-
const isLastAssistant = msg.role === 'assistant' && !streaming
|
|
645
|
-
&& filteredMessages.slice(i + 1).every((m) => m.role !== 'assistant')
|
|
646
|
-
const isSearchMatch = searchQuery && searchMatches.some((m) => m.i === i)
|
|
647
|
-
const isCurrentMatch = searchQuery && searchMatches[searchIdx]?.i === i
|
|
648
|
-
|
|
649
|
-
// Date separator
|
|
650
|
-
const prevMsg = i > 0 ? filteredMessages[i - 1] : null
|
|
651
|
-
const showDateSep = msg.time && (!prevMsg?.time || new Date(msg.time).toDateString() !== new Date(prevMsg.time).toDateString())
|
|
652
|
-
|
|
653
|
-
// Moment overlay — only on the last assistant message
|
|
654
|
-
let momentOverlay: React.ReactNode = null
|
|
655
|
-
if (isLastAssistant && currentMoment && !streaming) {
|
|
656
|
-
if (currentMoment.kind === 'heartbeat') {
|
|
657
|
-
momentOverlay = <HeartbeatMoment onDismiss={() => setCurrentMoment(null)} />
|
|
658
|
-
} else {
|
|
659
|
-
momentOverlay = (
|
|
660
|
-
<ActivityMoment
|
|
661
|
-
key={currentMoment.key}
|
|
662
|
-
toolName={currentMoment.name}
|
|
663
|
-
toolInput={currentMoment.input}
|
|
664
|
-
onDismiss={() => setCurrentMoment(null)}
|
|
665
|
-
/>
|
|
666
|
-
)
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
return (
|
|
671
|
-
<div
|
|
672
|
-
key={`${sessionId}-${msg.role}-${originalIndex >= 0 ? originalIndex : i}`}
|
|
673
|
-
data-message-index={i}
|
|
674
|
-
style={{
|
|
675
|
-
animation: `${msg.role === 'user' ? 'msg-in-right' : 'msg-in-left'} 0.4s var(--ease-spring) both`,
|
|
676
|
-
animationDelay: `${Math.min(i * 0.05, 0.4)}s`
|
|
677
|
-
}}
|
|
678
|
-
>
|
|
679
|
-
{showDateSep && (
|
|
680
|
-
<div className="flex items-center gap-4 py-2 mb-2">
|
|
681
|
-
<div className="flex-1 h-px bg-white/[0.06]" />
|
|
682
|
-
<span className="text-[10px] font-600 text-text-3/50 uppercase tracking-[0.1em]">
|
|
683
|
-
{dateSeparator(msg.time)}
|
|
684
|
-
</span>
|
|
685
|
-
<div className="flex-1 h-px bg-white/[0.06]" />
|
|
686
|
-
</div>
|
|
687
|
-
)}
|
|
688
|
-
<div className={isCurrentMatch ? 'ring-1 ring-amber-400/50 rounded-[16px] bg-amber-400/[0.04]' : isSearchMatch ? 'bg-white/[0.02] rounded-[16px]' : ''}>
|
|
689
|
-
<MessageBubble
|
|
690
|
-
message={msg}
|
|
691
|
-
assistantName={assistantName}
|
|
692
|
-
agentAvatarSeed={agent?.avatarSeed}
|
|
693
|
-
agentAvatarUrl={agent?.avatarUrl}
|
|
694
|
-
agentName={agent?.name}
|
|
695
|
-
isLast={isLastAssistant}
|
|
696
|
-
onRetry={isLastAssistant ? retryLastMessage : undefined}
|
|
697
|
-
messageIndex={originalIndex >= 0 ? originalIndex : undefined}
|
|
698
|
-
onToggleBookmark={toggleBookmark}
|
|
699
|
-
onEditResend={handleEditResend}
|
|
700
|
-
onFork={handleFork}
|
|
701
|
-
momentOverlay={momentOverlay}
|
|
702
|
-
/>
|
|
703
|
-
</div>
|
|
704
|
-
</div>
|
|
705
|
-
)
|
|
706
|
-
})}
|
|
835
|
+
{transcriptNodes}
|
|
707
836
|
<ApprovalCards agentId={agent?.id} />
|
|
708
|
-
|
|
709
|
-
|
|
837
|
+
<LiveStreamLane
|
|
838
|
+
streaming={streaming}
|
|
839
|
+
hasVisiblePersistedStreamingMessage={hasVisiblePersistedStreamingMessage}
|
|
840
|
+
assistantName={assistantName}
|
|
841
|
+
agentAvatarSeed={agent?.avatarSeed}
|
|
842
|
+
agentAvatarUrl={agent?.avatarUrl}
|
|
843
|
+
agentName={agent?.name}
|
|
844
|
+
/>
|
|
710
845
|
{appSettings.suggestionsEnabled === true && !streaming && filteredMessages.length > 0 && filteredMessages[filteredMessages.length - 1]?.role === 'assistant' && (
|
|
711
846
|
<SuggestionsBar lastMessage={filteredMessages[filteredMessages.length - 1]} onSend={sendMessage} />
|
|
712
847
|
)}
|