@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,54 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { fetchWithTimeout } from './fetch-timeout'
|
|
5
|
+
|
|
6
|
+
const originalFetch = global.fetch
|
|
7
|
+
const originalSetTimeout = global.setTimeout
|
|
8
|
+
const originalClearTimeout = global.clearTimeout
|
|
9
|
+
|
|
10
|
+
test.afterEach(() => {
|
|
11
|
+
global.fetch = originalFetch
|
|
12
|
+
global.setTimeout = originalSetTimeout
|
|
13
|
+
global.clearTimeout = originalClearTimeout
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('fetchWithTimeout throws TimeoutError with a clear message on timeout', async () => {
|
|
17
|
+
global.setTimeout = (((callback: (...args: unknown[]) => void) => {
|
|
18
|
+
queueMicrotask(() => callback())
|
|
19
|
+
return 1 as unknown as ReturnType<typeof setTimeout>
|
|
20
|
+
}) as typeof setTimeout)
|
|
21
|
+
global.clearTimeout = (() => {}) as typeof clearTimeout
|
|
22
|
+
global.fetch = (((_input: RequestInfo | URL, init?: RequestInit) => new Promise<Response>((_resolve, reject) => {
|
|
23
|
+
const onAbort = () => reject(init?.signal?.reason ?? new DOMException('Aborted', 'AbortError'))
|
|
24
|
+
if (init?.signal?.aborted) onAbort()
|
|
25
|
+
else init?.signal?.addEventListener('abort', onAbort, { once: true })
|
|
26
|
+
})) as typeof fetch)
|
|
27
|
+
|
|
28
|
+
await assert.rejects(
|
|
29
|
+
() => fetchWithTimeout('/slow', {}, 5_000),
|
|
30
|
+
(err: unknown) => {
|
|
31
|
+
assert.ok(err instanceof Error)
|
|
32
|
+
assert.equal(err.name, 'TimeoutError')
|
|
33
|
+
assert.match(err.message, /5000ms/)
|
|
34
|
+
return true
|
|
35
|
+
},
|
|
36
|
+
)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('fetchWithTimeout preserves caller abort signals', async () => {
|
|
40
|
+
const controller = new AbortController()
|
|
41
|
+
const expectedError = new DOMException('Manual cancel', 'AbortError')
|
|
42
|
+
controller.abort(expectedError)
|
|
43
|
+
global.fetch = (((_input: RequestInfo | URL, init?: RequestInit) => {
|
|
44
|
+
return Promise.reject(init?.signal?.reason ?? expectedError)
|
|
45
|
+
}) as typeof fetch)
|
|
46
|
+
|
|
47
|
+
await assert.rejects(
|
|
48
|
+
() => fetchWithTimeout('/aborted', { signal: controller.signal }, 5_000),
|
|
49
|
+
(err: unknown) => {
|
|
50
|
+
assert.strictEqual(err, expectedError)
|
|
51
|
+
return true
|
|
52
|
+
},
|
|
53
|
+
)
|
|
54
|
+
})
|
package/src/lib/fetch-timeout.ts
CHANGED
|
@@ -1,16 +1,73 @@
|
|
|
1
1
|
const MIN_TIMEOUT_MS = 1_000
|
|
2
2
|
|
|
3
|
+
function createTimeoutError(timeoutMs: number): Error {
|
|
4
|
+
const error = new Error(`Request timed out after ${timeoutMs}ms`)
|
|
5
|
+
error.name = 'TimeoutError'
|
|
6
|
+
return error
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function abortWithReason(controller: AbortController, reason: unknown): void {
|
|
10
|
+
try {
|
|
11
|
+
controller.abort(reason)
|
|
12
|
+
} catch {
|
|
13
|
+
controller.abort()
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function combineAbortSignals(signals: AbortSignal[]): AbortSignal {
|
|
18
|
+
if (signals.length === 1) return signals[0]
|
|
19
|
+
if (typeof AbortSignal.any === 'function') return AbortSignal.any(signals)
|
|
20
|
+
|
|
21
|
+
const controller = new AbortController()
|
|
22
|
+
const listeners = new Map<AbortSignal, () => void>()
|
|
23
|
+
const abortFrom = (signal: AbortSignal) => {
|
|
24
|
+
for (const [candidate, listener] of listeners.entries()) {
|
|
25
|
+
candidate.removeEventListener('abort', listener)
|
|
26
|
+
}
|
|
27
|
+
abortWithReason(controller, signal.reason)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for (const signal of signals) {
|
|
31
|
+
if (signal.aborted) {
|
|
32
|
+
abortFrom(signal)
|
|
33
|
+
break
|
|
34
|
+
}
|
|
35
|
+
const listener = () => abortFrom(signal)
|
|
36
|
+
listeners.set(signal, listener)
|
|
37
|
+
signal.addEventListener('abort', listener, { once: true })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return controller.signal
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isAbortError(err: unknown): boolean {
|
|
44
|
+
return Boolean(err) && typeof err === 'object' && (err as { name?: string }).name === 'AbortError'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function isTimeoutError(err: unknown): boolean {
|
|
48
|
+
return Boolean(err) && typeof err === 'object' && (err as { name?: string }).name === 'TimeoutError'
|
|
49
|
+
}
|
|
50
|
+
|
|
3
51
|
export async function fetchWithTimeout(
|
|
4
52
|
input: RequestInfo | URL,
|
|
5
53
|
init: RequestInit = {},
|
|
6
54
|
timeoutMs: number,
|
|
7
55
|
): Promise<Response> {
|
|
8
56
|
const boundedTimeout = Math.max(MIN_TIMEOUT_MS, Math.trunc(timeoutMs))
|
|
9
|
-
const
|
|
10
|
-
const
|
|
57
|
+
const timeoutController = new AbortController()
|
|
58
|
+
const timeoutError = createTimeoutError(boundedTimeout)
|
|
59
|
+
const signal = init.signal
|
|
60
|
+
? combineAbortSignals([init.signal, timeoutController.signal])
|
|
61
|
+
: timeoutController.signal
|
|
62
|
+
const timer = setTimeout(() => abortWithReason(timeoutController, timeoutError), boundedTimeout)
|
|
11
63
|
|
|
12
64
|
try {
|
|
13
|
-
return await fetch(input, { ...init, signal
|
|
65
|
+
return await fetch(input, { ...init, signal })
|
|
66
|
+
} catch (err) {
|
|
67
|
+
if (timeoutController.signal.aborted && isTimeoutError(timeoutController.signal.reason)) {
|
|
68
|
+
throw timeoutController.signal.reason
|
|
69
|
+
}
|
|
70
|
+
throw err
|
|
14
71
|
} finally {
|
|
15
72
|
clearTimeout(timer)
|
|
16
73
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { applyStreamingToolCall, applyStreamingToolResult, isLikelyToolErrorOutput } from './live-tool-events'
|
|
5
|
+
|
|
6
|
+
describe('live tool events', () => {
|
|
7
|
+
it('dedupes consecutive identical pending tool calls', () => {
|
|
8
|
+
const first = applyStreamingToolCall([], {
|
|
9
|
+
toolName: 'browser',
|
|
10
|
+
toolInput: '{"action":"navigate","url":"https://example.com"}',
|
|
11
|
+
toolCallId: 'call-1',
|
|
12
|
+
}, 'fallback-1')
|
|
13
|
+
|
|
14
|
+
const second = applyStreamingToolCall(first, {
|
|
15
|
+
toolName: 'browser',
|
|
16
|
+
toolInput: '{"action":"navigate","url":"https://example.com"}',
|
|
17
|
+
toolCallId: 'call-1',
|
|
18
|
+
}, 'fallback-2')
|
|
19
|
+
|
|
20
|
+
assert.equal(second.length, 1)
|
|
21
|
+
assert.equal(second[0]?.id, 'call-1')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('matches parallel same-tool results by toolCallId', () => {
|
|
25
|
+
const withCalls = applyStreamingToolCall(
|
|
26
|
+
applyStreamingToolCall([], {
|
|
27
|
+
toolName: 'read_file',
|
|
28
|
+
toolInput: '{"path":"a.txt"}',
|
|
29
|
+
toolCallId: 'call-a',
|
|
30
|
+
}, 'fallback-a'),
|
|
31
|
+
{
|
|
32
|
+
toolName: 'read_file',
|
|
33
|
+
toolInput: '{"path":"b.txt"}',
|
|
34
|
+
toolCallId: 'call-b',
|
|
35
|
+
},
|
|
36
|
+
'fallback-b',
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
const withFirstResult = applyStreamingToolResult(withCalls, {
|
|
40
|
+
toolName: 'read_file',
|
|
41
|
+
toolOutput: 'contents of b',
|
|
42
|
+
toolCallId: 'call-b',
|
|
43
|
+
})
|
|
44
|
+
const withBothResults = applyStreamingToolResult(withFirstResult, {
|
|
45
|
+
toolName: 'read_file',
|
|
46
|
+
toolOutput: 'contents of a',
|
|
47
|
+
toolCallId: 'call-a',
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
assert.equal(withBothResults[0]?.output, 'contents of a')
|
|
51
|
+
assert.equal(withBothResults[0]?.status, 'done')
|
|
52
|
+
assert.equal(withBothResults[1]?.output, 'contents of b')
|
|
53
|
+
assert.equal(withBothResults[1]?.status, 'done')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('uses fallback matching when toolCallId is missing', () => {
|
|
57
|
+
const withCall = applyStreamingToolCall([], {
|
|
58
|
+
toolName: 'browser',
|
|
59
|
+
toolInput: '{"action":"navigate","url":"https://example.com"}',
|
|
60
|
+
}, 'fallback-nav')
|
|
61
|
+
|
|
62
|
+
const withResult = applyStreamingToolResult(withCall, {
|
|
63
|
+
toolName: 'browser',
|
|
64
|
+
toolOutput: 'navigated successfully',
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
assert.equal(withResult[0]?.id, 'fallback-nav')
|
|
68
|
+
assert.equal(withResult[0]?.status, 'done')
|
|
69
|
+
assert.equal(withResult[0]?.output, 'navigated successfully')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('detects common tool error outputs', () => {
|
|
73
|
+
assert.equal(isLikelyToolErrorOutput('Error: command failed'), true)
|
|
74
|
+
assert.equal(isLikelyToolErrorOutput('timeout waiting for response'), true)
|
|
75
|
+
assert.equal(isLikelyToolErrorOutput('File written successfully'), false)
|
|
76
|
+
})
|
|
77
|
+
})
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { SSEEvent } from '@/types'
|
|
2
|
+
|
|
3
|
+
export interface StreamingToolEvent {
|
|
4
|
+
id: string
|
|
5
|
+
name: string
|
|
6
|
+
input: string
|
|
7
|
+
output?: string
|
|
8
|
+
status: 'running' | 'done' | 'error'
|
|
9
|
+
toolCallId?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function isLikelyToolErrorOutput(output: string): boolean {
|
|
13
|
+
const trimmed = output.trim()
|
|
14
|
+
return /^(Error:|error:|ECONNREFUSED|ETIMEDOUT|timeout|failed)/i.test(trimmed)
|
|
15
|
+
|| output.includes('ECONNREFUSED')
|
|
16
|
+
|| output.includes('ETIMEDOUT')
|
|
17
|
+
|| output.includes('Error:')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function applyStreamingToolCall(
|
|
21
|
+
events: StreamingToolEvent[],
|
|
22
|
+
ev: Pick<SSEEvent, 'toolName' | 'toolInput' | 'toolCallId'>,
|
|
23
|
+
fallbackId: string,
|
|
24
|
+
): StreamingToolEvent[] {
|
|
25
|
+
const previous = events[events.length - 1]
|
|
26
|
+
const name = ev.toolName || 'unknown'
|
|
27
|
+
const input = ev.toolInput || ''
|
|
28
|
+
|
|
29
|
+
if (
|
|
30
|
+
previous
|
|
31
|
+
&& previous.name === name
|
|
32
|
+
&& previous.input === input
|
|
33
|
+
&& previous.status === 'running'
|
|
34
|
+
&& previous.toolCallId === (ev.toolCallId || previous.toolCallId)
|
|
35
|
+
) {
|
|
36
|
+
return events
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return [...events, {
|
|
40
|
+
id: ev.toolCallId || fallbackId,
|
|
41
|
+
name,
|
|
42
|
+
input,
|
|
43
|
+
status: 'running',
|
|
44
|
+
toolCallId: ev.toolCallId,
|
|
45
|
+
}]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function applyStreamingToolResult(
|
|
49
|
+
events: StreamingToolEvent[],
|
|
50
|
+
ev: Pick<SSEEvent, 'toolName' | 'toolOutput' | 'toolCallId'>,
|
|
51
|
+
): StreamingToolEvent[] {
|
|
52
|
+
const index = ev.toolCallId
|
|
53
|
+
? events.findLastIndex((entry) => entry.toolCallId === ev.toolCallId && entry.status === 'running')
|
|
54
|
+
: events.findLastIndex((entry) => entry.name === (ev.toolName || 'unknown') && entry.status === 'running')
|
|
55
|
+
|
|
56
|
+
if (index === -1) return events
|
|
57
|
+
|
|
58
|
+
const output = ev.toolOutput || ''
|
|
59
|
+
const nextStatus = isLikelyToolErrorOutput(output) ? 'error' : 'done'
|
|
60
|
+
const current = events[index]
|
|
61
|
+
|
|
62
|
+
if (current.output === output && current.status === nextStatus) {
|
|
63
|
+
return events
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const next = [...events]
|
|
67
|
+
next[index] = {
|
|
68
|
+
...current,
|
|
69
|
+
output,
|
|
70
|
+
status: nextStatus,
|
|
71
|
+
}
|
|
72
|
+
return next
|
|
73
|
+
}
|
|
@@ -64,10 +64,10 @@ describe('local observability', () => {
|
|
|
64
64
|
})
|
|
65
65
|
|
|
66
66
|
it('detects localhost browser hosts', () => {
|
|
67
|
-
globalThis.window = { location: { hostname:
|
|
67
|
+
globalThis.window = { location: { hostname: "localhost" } } as any
|
|
68
68
|
assert.equal(isLocalhostBrowser(), true)
|
|
69
69
|
|
|
70
|
-
globalThis.window = { location: { hostname:
|
|
70
|
+
globalThis.window = { location: { hostname: "swarmclaw.ai" } } as any
|
|
71
71
|
assert.equal(isLocalhostBrowser(), false)
|
|
72
72
|
})
|
|
73
73
|
})
|
|
@@ -2,6 +2,7 @@ import fs from 'fs'
|
|
|
2
2
|
import https from 'https'
|
|
3
3
|
import type { StreamChatOptions } from './index'
|
|
4
4
|
import { PROVIDER_DEFAULTS } from './provider-defaults'
|
|
5
|
+
import { resolveImagePath } from '@/lib/server/resolve-image'
|
|
5
6
|
|
|
6
7
|
const IMAGE_EXTS = /\.(png|jpg|jpeg|gif|webp|bmp)$/i
|
|
7
8
|
const TEXT_EXTS = /\.(txt|md|csv|json|xml|html|js|ts|tsx|jsx|py|go|rs|java|c|cpp|h|yml|yaml|toml|env|log|sh|sql|css|scss)$/i
|
|
@@ -24,9 +25,9 @@ function fileToContentBlocks(filePath: string): any[] {
|
|
|
24
25
|
return [{ type: 'text', text: `[Attached file: ${filePath.split('/').pop()}]` }]
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
export function streamAnthropicChat({ session, message, imagePath,
|
|
28
|
+
export function streamAnthropicChat({ session, message, imagePath, apiKey, systemPrompt, write, active, loadHistory, onUsage, signal }: StreamChatOptions): Promise<string> {
|
|
28
29
|
return new Promise((resolve) => {
|
|
29
|
-
const messages = buildMessages(session, message, imagePath, loadHistory
|
|
30
|
+
const messages = buildMessages(session, message, imagePath, loadHistory)
|
|
30
31
|
const model = session.model || 'claude-sonnet-4-6'
|
|
31
32
|
let usageInput = 0
|
|
32
33
|
let usageOutput = 0
|
|
@@ -136,30 +137,25 @@ export function streamAnthropicChat({ session, message, imagePath, imageUrl, api
|
|
|
136
137
|
})
|
|
137
138
|
}
|
|
138
139
|
|
|
139
|
-
function
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function buildMessages(session: any, message: string, imagePath: string | undefined, loadHistory: (id: string) => any[], imageUrl?: string) {
|
|
144
|
-
const msgs: Array<{ role: string; content: any }> = []
|
|
140
|
+
function buildMessages(session: Record<string, unknown> & { id: string }, message: string, imagePath: string | undefined, loadHistory: (id: string) => Record<string, unknown>[]) {
|
|
141
|
+
const msgs: Array<{ role: string; content: unknown }> = []
|
|
145
142
|
|
|
146
143
|
if (loadHistory) {
|
|
147
144
|
const history = loadHistory(session.id).slice(-40)
|
|
148
145
|
for (const m of history) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
146
|
+
const histImagePath = resolveImagePath(m.imagePath as string | undefined, m.imageUrl as string | undefined)
|
|
147
|
+
if (m.role === 'user' && histImagePath) {
|
|
148
|
+
const blocks = fileToContentBlocks(histImagePath)
|
|
152
149
|
msgs.push({ role: 'user', content: [...blocks, { type: 'text', text: m.text }] })
|
|
153
150
|
} else {
|
|
154
|
-
msgs.push({ role: m.role, content: m.text })
|
|
151
|
+
msgs.push({ role: m.role as string, content: m.text })
|
|
155
152
|
}
|
|
156
153
|
}
|
|
157
154
|
}
|
|
158
155
|
|
|
159
|
-
// Current message with optional attachment
|
|
160
|
-
if (imagePath
|
|
161
|
-
const blocks =
|
|
162
|
-
if (imageUrl) blocks.push(urlToImageBlock(imageUrl))
|
|
156
|
+
// Current message with optional attachment (imagePath already resolved by caller)
|
|
157
|
+
if (imagePath) {
|
|
158
|
+
const blocks = fileToContentBlocks(imagePath)
|
|
163
159
|
msgs.push({ role: 'user', content: [...blocks, { type: 'text', text: message }] })
|
|
164
160
|
} else {
|
|
165
161
|
msgs.push({ role: 'user', content: message })
|
|
@@ -5,6 +5,8 @@ import { streamOpenAiChat } from './openai'
|
|
|
5
5
|
import { streamOllamaChat } from './ollama'
|
|
6
6
|
import { streamAnthropicChat } from './anthropic'
|
|
7
7
|
import { streamOpenClawChat } from './openclaw'
|
|
8
|
+
import { errorMessage } from '../shared-utils'
|
|
9
|
+
import { sleep } from '@/lib/shared-utils'
|
|
8
10
|
import type { ProviderInfo, ProviderConfig as CustomProviderConfig, ProviderType } from '../../types'
|
|
9
11
|
|
|
10
12
|
const RETRYABLE_STATUS_CODES = [401, 429, 500, 502, 503]
|
|
@@ -392,7 +394,7 @@ export async function streamChatWithFailover(
|
|
|
392
394
|
lastError = err
|
|
393
395
|
const errObj = err as Record<string, unknown>
|
|
394
396
|
const statusCode = (typeof errObj?.status === 'number' ? errObj.status : typeof errObj?.statusCode === 'number' ? errObj.statusCode : 0) as number
|
|
395
|
-
const errMessage =
|
|
397
|
+
const errMessage = errorMessage(err)
|
|
396
398
|
const isRetryable = RETRYABLE_STATUS_CODES.includes(statusCode)
|
|
397
399
|
|| errMessage?.includes('rate limit')
|
|
398
400
|
|| errMessage?.includes('Rate limit')
|
|
@@ -409,7 +411,7 @@ export async function streamChatWithFailover(
|
|
|
409
411
|
// Exponential backoff for rate-limit / server errors (skip for auth rotation)
|
|
410
412
|
if (statusCode !== 401) {
|
|
411
413
|
const delay = Math.min(500 * Math.pow(2, i), 8000)
|
|
412
|
-
await
|
|
414
|
+
await sleep(delay)
|
|
413
415
|
}
|
|
414
416
|
continue
|
|
415
417
|
}
|
|
@@ -3,6 +3,7 @@ import http from 'http'
|
|
|
3
3
|
import https from 'https'
|
|
4
4
|
import type { StreamChatOptions } from './index'
|
|
5
5
|
import { resolveOllamaRuntimeConfig } from '@/lib/server/ollama-runtime'
|
|
6
|
+
import { resolveImagePath } from '@/lib/server/resolve-image'
|
|
6
7
|
|
|
7
8
|
const IMAGE_EXTS = /\.(png|jpg|jpeg|gif|webp|bmp)$/i
|
|
8
9
|
const TEXT_EXTS = /\.(txt|md|csv|json|xml|html|js|ts|tsx|jsx|py|go|rs|java|c|cpp|h|yml|yaml|toml|env|log|sh|sql|css|scss)$/i
|
|
@@ -144,20 +145,22 @@ function fileToOllamaMsg(text: string, filePath?: string): { content: string; im
|
|
|
144
145
|
return { content: `[Attached file: ${filePath.split('/').pop()}]\n\n${text}` }
|
|
145
146
|
}
|
|
146
147
|
|
|
147
|
-
function buildMessages(session:
|
|
148
|
+
function buildMessages(session: Record<string, unknown>, message: string, imagePath: string | undefined, loadHistory: (id: string) => Record<string, unknown>[]) {
|
|
148
149
|
const msgs: Array<{ role: string; content: string; images?: string[] }> = []
|
|
149
150
|
|
|
150
151
|
if (loadHistory) {
|
|
151
|
-
const history = loadHistory(session.id).slice(-40)
|
|
152
|
+
const history = loadHistory(session.id as string).slice(-40)
|
|
152
153
|
for (const m of history) {
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
const histImagePath = resolveImagePath(m.imagePath as string | undefined, m.imageUrl as string | undefined)
|
|
155
|
+
if (m.role === 'user' && histImagePath) {
|
|
156
|
+
msgs.push({ role: 'user', ...fileToOllamaMsg(m.text as string, histImagePath) })
|
|
155
157
|
} else {
|
|
156
|
-
msgs.push({ role: m.role, content: m.text })
|
|
158
|
+
msgs.push({ role: m.role as string, content: m.text as string })
|
|
157
159
|
}
|
|
158
160
|
}
|
|
159
161
|
}
|
|
160
162
|
|
|
161
|
-
|
|
163
|
+
const resolvedPath = resolveImagePath(imagePath)
|
|
164
|
+
msgs.push({ role: 'user', ...fileToOllamaMsg(message, resolvedPath ?? undefined) })
|
|
162
165
|
return msgs
|
|
163
166
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs'
|
|
2
2
|
import type { StreamChatOptions } from './index'
|
|
3
3
|
import { PROVIDER_DEFAULTS } from './provider-defaults'
|
|
4
|
+
import { resolveImagePath } from '@/lib/server/resolve-image'
|
|
4
5
|
|
|
5
6
|
const IMAGE_EXTS = /\.(png|jpg|jpeg|gif|webp|bmp)$/i
|
|
6
7
|
const TEXT_EXTS = /\.(txt|md|csv|json|xml|html|js|ts|tsx|jsx|py|go|rs|java|c|cpp|h|yml|yaml|toml|env|log|sh|sql|css|scss)$/i
|
|
@@ -168,34 +169,30 @@ export function streamOpenAiChat({ session, message, imagePath, imageUrl, apiKey
|
|
|
168
169
|
})
|
|
169
170
|
}
|
|
170
171
|
|
|
171
|
-
function
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async function buildMessages(session: any, message: string, imagePath: string | undefined, systemPrompt: string | undefined, loadHistory: (id: string) => any[], imageUrl?: string) {
|
|
176
|
-
const msgs: Array<{ role: string; content: any }> = []
|
|
172
|
+
async function buildMessages(session: Record<string, unknown>, message: string, imagePath: string | undefined, systemPrompt: string | undefined, loadHistory: (id: string) => Record<string, unknown>[], imageUrl?: string) {
|
|
173
|
+
const msgs: Array<{ role: string; content: unknown }> = []
|
|
177
174
|
|
|
178
175
|
if (systemPrompt) {
|
|
179
176
|
msgs.push({ role: 'system', content: systemPrompt })
|
|
180
177
|
}
|
|
181
178
|
|
|
182
179
|
if (loadHistory) {
|
|
183
|
-
const history = loadHistory(session.id).slice(-40)
|
|
180
|
+
const history = loadHistory(session.id as string).slice(-40)
|
|
184
181
|
for (const m of history) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
182
|
+
const histImagePath = resolveImagePath(m.imagePath as string | undefined, m.imageUrl as string | undefined)
|
|
183
|
+
if (m.role === 'user' && histImagePath) {
|
|
184
|
+
const parts = await fileToContentParts(histImagePath)
|
|
188
185
|
msgs.push({ role: 'user', content: [...parts, { type: 'text', text: m.text }] })
|
|
189
186
|
} else {
|
|
190
|
-
msgs.push({ role: m.role, content: m.text })
|
|
187
|
+
msgs.push({ role: m.role as string, content: m.text })
|
|
191
188
|
}
|
|
192
189
|
}
|
|
193
190
|
}
|
|
194
191
|
|
|
195
192
|
// Current message with optional attachment
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
193
|
+
const resolvedPath = resolveImagePath(imagePath, imageUrl)
|
|
194
|
+
if (resolvedPath) {
|
|
195
|
+
const parts = await fileToContentParts(resolvedPath)
|
|
199
196
|
msgs.push({ role: 'user', content: [...parts, { type: 'text', text: message }] })
|
|
200
197
|
} else {
|
|
201
198
|
msgs.push({ role: 'user', content: message })
|
|
@@ -5,24 +5,24 @@ import { isDevelopmentLikeRuntime, isProductionRuntime } from './runtime-env'
|
|
|
5
5
|
|
|
6
6
|
describe('runtime env helpers', () => {
|
|
7
7
|
it('treats missing NODE_ENV as development-like', () => {
|
|
8
|
-
const previousNodeEnv = process.env.NODE_ENV
|
|
9
|
-
delete process.env.NODE_ENV
|
|
8
|
+
const previousNodeEnv = process.env.NODE_ENV;
|
|
9
|
+
delete (process.env as any).NODE_ENV
|
|
10
10
|
|
|
11
11
|
assert.equal(isDevelopmentLikeRuntime(), true)
|
|
12
12
|
assert.equal(isProductionRuntime(), false)
|
|
13
13
|
|
|
14
|
-
if (previousNodeEnv === undefined) delete process.env.NODE_ENV
|
|
15
|
-
else process.env.NODE_ENV = previousNodeEnv
|
|
14
|
+
if (previousNodeEnv === undefined) delete (process.env as any).NODE_ENV
|
|
15
|
+
else (process.env as any).NODE_ENV = previousNodeEnv
|
|
16
16
|
})
|
|
17
17
|
|
|
18
18
|
it('detects explicit production mode', () => {
|
|
19
|
-
const previousNodeEnv = process.env.NODE_ENV
|
|
20
|
-
process.env.NODE_ENV = 'production'
|
|
19
|
+
const previousNodeEnv = process.env.NODE_ENV;
|
|
20
|
+
(process.env as any).NODE_ENV = 'production'
|
|
21
21
|
|
|
22
22
|
assert.equal(isDevelopmentLikeRuntime(), false)
|
|
23
23
|
assert.equal(isProductionRuntime(), true)
|
|
24
24
|
|
|
25
|
-
if (previousNodeEnv === undefined) delete process.env.NODE_ENV
|
|
26
|
-
else process.env.NODE_ENV = previousNodeEnv
|
|
25
|
+
if (previousNodeEnv === undefined) delete (process.env as any).NODE_ENV
|
|
26
|
+
else (process.env as any).NODE_ENV = previousNodeEnv
|
|
27
27
|
})
|
|
28
28
|
})
|
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
findEquivalentSchedules,
|
|
6
6
|
getScheduleSignatureKey,
|
|
7
7
|
type ScheduleLike,
|
|
8
|
-
} from './schedule-dedupe
|
|
8
|
+
} from './schedule-dedupe'
|
|
9
9
|
import {
|
|
10
10
|
isAgentCreatedSchedule,
|
|
11
11
|
shouldAutoDeleteScheduleAfterTerminalRun,
|
|
12
|
-
} from './schedule-origin
|
|
12
|
+
} from './schedule-origin'
|
|
13
13
|
import type { Schedule } from '@/types'
|
|
14
14
|
|
|
15
15
|
function makeSchedule(partial?: Partial<ScheduleLike>): ScheduleLike {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert/strict'
|
|
2
2
|
import { test } from 'node:test'
|
|
3
|
-
import { findDuplicateSchedule, findEquivalentSchedules, getScheduleSignatureKey, type ScheduleLike } from './schedule-dedupe
|
|
3
|
+
import { findDuplicateSchedule, findEquivalentSchedules, getScheduleSignatureKey, type ScheduleLike } from './schedule-dedupe'
|
|
4
4
|
|
|
5
5
|
test('findDuplicateSchedule matches active interval schedules with normalized prompts', () => {
|
|
6
6
|
const schedules: Record<string, ScheduleLike> = {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CronExpressionParser } from 'cron-parser'
|
|
2
2
|
import type { ScheduleType } from '@/types'
|
|
3
|
+
import { dedup } from '@/lib/shared-utils'
|
|
3
4
|
|
|
4
5
|
export type ScheduleLike = {
|
|
5
6
|
id?: string
|
|
@@ -242,8 +243,8 @@ function countTokenOverlap(a: string[], b: string[]): number {
|
|
|
242
243
|
|
|
243
244
|
function hasFuzzyPromptMatch(a: ScheduleSignature, b: ScheduleSignature): boolean {
|
|
244
245
|
if (!a.promptTokens.length || !b.promptTokens.length) return false
|
|
245
|
-
const uniqueA =
|
|
246
|
-
const uniqueB =
|
|
246
|
+
const uniqueA = dedup(a.promptTokens)
|
|
247
|
+
const uniqueB = dedup(b.promptTokens)
|
|
247
248
|
const overlap = countTokenOverlap(uniqueA, uniqueB)
|
|
248
249
|
if (overlap === 0) return false
|
|
249
250
|
const smallerSize = Math.min(uniqueA.length, uniqueB.length)
|
|
@@ -36,9 +36,9 @@ function runWithTempDataDir(script: string) {
|
|
|
36
36
|
describe('ensureAgentThreadSession', () => {
|
|
37
37
|
it('creates and reuses an agent shortcut chat for heartbeat-enabled agents', () => {
|
|
38
38
|
const output = runWithTempDataDir(`
|
|
39
|
-
const storageMod = await import('./src/lib/server/storage
|
|
39
|
+
const storageMod = await import('./src/lib/server/storage')
|
|
40
40
|
const storage = storageMod.default || storageMod['module.exports'] || storageMod
|
|
41
|
-
const helperMod = await import('./src/lib/server/agent-thread-session
|
|
41
|
+
const helperMod = await import('./src/lib/server/agent-thread-session')
|
|
42
42
|
const ensureAgentThreadSession = helperMod.ensureAgentThreadSession
|
|
43
43
|
|| helperMod.default?.ensureAgentThreadSession
|
|
44
44
|
|| helperMod['module.exports']?.ensureAgentThreadSession
|
|
@@ -91,9 +91,9 @@ describe('ensureAgentThreadSession', () => {
|
|
|
91
91
|
|
|
92
92
|
it('does not create a new shortcut chat when the agent is disabled', () => {
|
|
93
93
|
const output = runWithTempDataDir(`
|
|
94
|
-
const storageMod = await import('./src/lib/server/storage
|
|
94
|
+
const storageMod = await import('./src/lib/server/storage')
|
|
95
95
|
const storage = storageMod.default || storageMod['module.exports'] || storageMod
|
|
96
|
-
const helperMod = await import('./src/lib/server/agent-thread-session
|
|
96
|
+
const helperMod = await import('./src/lib/server/agent-thread-session')
|
|
97
97
|
const ensureAgentThreadSession = helperMod.ensureAgentThreadSession
|
|
98
98
|
|| helperMod.default?.ensureAgentThreadSession
|
|
99
99
|
|| helperMod['module.exports']?.ensureAgentThreadSession
|
|
@@ -136,9 +136,9 @@ describe('ensureAgentThreadSession', () => {
|
|
|
136
136
|
|
|
137
137
|
it('propagates explicit OpenClaw gateway agent ids into the shortcut session', () => {
|
|
138
138
|
const output = runWithTempDataDir(`
|
|
139
|
-
const storageMod = await import('./src/lib/server/storage
|
|
139
|
+
const storageMod = await import('./src/lib/server/storage')
|
|
140
140
|
const storage = storageMod.default || storageMod['module.exports'] || storageMod
|
|
141
|
-
const helperMod = await import('./src/lib/server/agent-thread-session
|
|
141
|
+
const helperMod = await import('./src/lib/server/agent-thread-session')
|
|
142
142
|
const ensureAgentThreadSession = helperMod.ensureAgentThreadSession
|
|
143
143
|
|| helperMod.default?.ensureAgentThreadSession
|
|
144
144
|
|| helperMod['module.exports']?.ensureAgentThreadSession
|
|
@@ -3,7 +3,7 @@ import type { Agent, Session } from '@/types'
|
|
|
3
3
|
import { applyResolvedRoute, resolvePrimaryAgentRoute } from './agent-runtime-config'
|
|
4
4
|
import { isAgentDisabled } from './agent-availability'
|
|
5
5
|
import { WORKSPACE_DIR } from './data-dir'
|
|
6
|
-
import { loadAgents, loadSessions,
|
|
6
|
+
import { loadAgents, loadSessions, upsertStoredItem } from './storage'
|
|
7
7
|
|
|
8
8
|
function buildEmptyDelegateResumeIds(): NonNullable<Session['delegateResumeIds']> {
|
|
9
9
|
return {
|
|
@@ -105,8 +105,7 @@ export function ensureAgentThreadSession(agentId: string, user = 'default'): Ses
|
|
|
105
105
|
const existingId = typeof agent.threadSessionId === 'string' ? agent.threadSessionId : ''
|
|
106
106
|
if (existingId && sessions[existingId]) {
|
|
107
107
|
const session = buildThreadSession(agent, existingId, user, now, sessions[existingId] as Session)
|
|
108
|
-
sessions
|
|
109
|
-
saveSessions(sessions)
|
|
108
|
+
upsertStoredItem('sessions', existingId, session)
|
|
110
109
|
return session
|
|
111
110
|
}
|
|
112
111
|
|
|
@@ -118,10 +117,9 @@ export function ensureAgentThreadSession(agentId: string, user = 'default'): Ses
|
|
|
118
117
|
if (legacySession) {
|
|
119
118
|
agent.threadSessionId = legacySession.id
|
|
120
119
|
agent.updatedAt = now
|
|
121
|
-
|
|
120
|
+
upsertStoredItem('agents', agentId, agent)
|
|
122
121
|
const session = buildThreadSession(agent, legacySession.id, user, now, legacySession)
|
|
123
|
-
sessions
|
|
124
|
-
saveSessions(sessions)
|
|
122
|
+
upsertStoredItem('sessions', legacySession.id, session)
|
|
125
123
|
return session
|
|
126
124
|
}
|
|
127
125
|
|
|
@@ -129,11 +127,10 @@ export function ensureAgentThreadSession(agentId: string, user = 'default'): Ses
|
|
|
129
127
|
|
|
130
128
|
const sessionId = `agent-chat-${agentId}-${genId()}`
|
|
131
129
|
const session = buildThreadSession(agent, sessionId, user, now)
|
|
132
|
-
sessions
|
|
133
|
-
saveSessions(sessions)
|
|
130
|
+
upsertStoredItem('sessions', sessionId, session)
|
|
134
131
|
|
|
135
132
|
agent.threadSessionId = sessionId
|
|
136
133
|
agent.updatedAt = now
|
|
137
|
-
|
|
134
|
+
upsertStoredItem('agents', agentId, agent)
|
|
138
135
|
return session
|
|
139
136
|
}
|