@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,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs'
|
|
2
2
|
import os from 'os'
|
|
3
|
-
import
|
|
3
|
+
import { perf } from './perf'
|
|
4
4
|
import {
|
|
5
5
|
loadSessions,
|
|
6
6
|
saveSessions,
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
loadAgents,
|
|
11
11
|
loadSkills,
|
|
12
12
|
loadSettings,
|
|
13
|
-
loadUsage,
|
|
14
13
|
appendUsage,
|
|
15
14
|
active,
|
|
16
15
|
} from './storage'
|
|
@@ -20,52 +19,102 @@ import { log } from './logger'
|
|
|
20
19
|
import { logExecution } from './execution-log'
|
|
21
20
|
import { buildToolDisciplineLines, streamAgentChat } from './stream-agent-chat'
|
|
22
21
|
import { runLinkUnderstanding } from './link-understanding'
|
|
23
|
-
import { buildSessionTools } from './session-tools'
|
|
24
|
-
import type { StructuredToolInterface } from '@langchain/core/tools'
|
|
25
22
|
import type { Session } from '@/types'
|
|
23
|
+
import type { ApprovalCategory } from '@/types'
|
|
26
24
|
import { stripMainLoopMetaForPersistence } from './main-agent-loop'
|
|
27
25
|
import { getPluginManager } from './plugins'
|
|
28
26
|
import { isLocalOpenClawEndpoint, normalizeProviderEndpoint } from '@/lib/openclaw-endpoint'
|
|
29
|
-
import { routeTaskIntent } from './capability-router'
|
|
30
27
|
import { notify } from './ws-hub'
|
|
31
28
|
import { applyResolvedRoute, resolvePrimaryAgentRoute } from './agent-runtime-config'
|
|
32
|
-
import {
|
|
33
|
-
import { pluginIdMatches } from './tool-aliases'
|
|
29
|
+
import { resolveSessionToolPolicy } from './tool-capability-policy'
|
|
34
30
|
import { buildCurrentDateTimePromptContext } from './prompt-runtime-context'
|
|
31
|
+
import { buildWorkspaceContext } from './workspace-context'
|
|
32
|
+
import { resolveImagePath } from './resolve-image'
|
|
33
|
+
import {
|
|
34
|
+
applyContextClearBoundary,
|
|
35
|
+
shouldApplySessionFreshnessReset,
|
|
36
|
+
shouldAutoRouteHeartbeatAlerts,
|
|
37
|
+
shouldPersistInboundUserMessage,
|
|
38
|
+
translateRequestedToolInvocation,
|
|
39
|
+
normalizeAssistantArtifactLinks,
|
|
40
|
+
extractHeartbeatStatus,
|
|
41
|
+
shouldReplaceRecentAssistantMessage,
|
|
42
|
+
hasPersistableAssistantPayload,
|
|
43
|
+
getPersistedAssistantText,
|
|
44
|
+
getToolEventsSnapshotKey,
|
|
45
|
+
requestedToolNamesFromMessage,
|
|
46
|
+
hasDirectLocalCodingTools,
|
|
47
|
+
parseUsdLimit,
|
|
48
|
+
getTodaySpendUsd,
|
|
49
|
+
classifyHeartbeatResponse,
|
|
50
|
+
estimateConversationTone,
|
|
51
|
+
} from './chat-execution-utils'
|
|
52
|
+
import { runPostLlmToolRouting } from './chat-turn-tool-routing'
|
|
35
53
|
import {
|
|
36
54
|
getCachedLlmResponse,
|
|
37
55
|
resolveLlmResponseCacheConfig,
|
|
38
56
|
setCachedLlmResponse,
|
|
39
57
|
type LlmResponseCacheKeyInput,
|
|
40
58
|
} from './llm-response-cache'
|
|
41
|
-
import { genId } from '@/lib/id'
|
|
42
59
|
import type { Message, MessageToolEvent, SSEEvent, UsageRecord } from '@/types'
|
|
43
|
-
import { markProviderFailure, markProviderSuccess
|
|
60
|
+
import { markProviderFailure, markProviderSuccess } from './provider-health'
|
|
44
61
|
import { isHeartbeatSource, isInternalHeartbeatRun } from './heartbeat-source'
|
|
45
62
|
import { NON_LANGGRAPH_PROVIDER_IDS } from '@/lib/provider-sets'
|
|
46
63
|
import { buildIdentityContinuityContext, refreshSessionIdentityState } from './identity-continuity'
|
|
47
64
|
import { syncSessionArchiveMemory } from './session-archive-memory'
|
|
48
65
|
import { evaluateSessionFreshness, resetSessionRuntime, resolveSessionResetPolicy } from './session-reset-policy'
|
|
49
66
|
import { pruneStreamingAssistantArtifacts, upsertStreamingAssistantArtifact } from '@/lib/chat-streaming-state'
|
|
50
|
-
import { resolveActiveProjectContext } from './project-context'
|
|
51
67
|
import { shouldSuppressHiddenControlText, stripHiddenControlTokens } from './assistant-control'
|
|
52
|
-
import { buildToolEventAssistantSummary } from '@/lib/tool-event-summary'
|
|
53
68
|
import { buildAgentDisabledMessage, isAgentDisabled } from './agent-availability'
|
|
54
|
-
|
|
69
|
+
import { errorMessage as toErrorMessage } from '@/lib/shared-utils'
|
|
70
|
+
|
|
71
|
+
export {
|
|
72
|
+
shouldApplySessionFreshnessReset,
|
|
73
|
+
shouldAutoRouteHeartbeatAlerts,
|
|
74
|
+
translateRequestedToolInvocation,
|
|
75
|
+
normalizeAssistantArtifactLinks,
|
|
76
|
+
requestedToolNamesFromMessage,
|
|
77
|
+
hasDirectLocalCodingTools,
|
|
78
|
+
}
|
|
55
79
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (messages[i].kind === 'context-clear') return filterModelHistory(messages.slice(i + 1))
|
|
61
|
-
}
|
|
62
|
-
return filterModelHistory(messages)
|
|
80
|
+
export function buildAgentRuntimeCapabilities(enabledPlugins: string[]): string[] {
|
|
81
|
+
const capabilities = ['heartbeats', 'autonomous_loop', 'multi_agent_chat']
|
|
82
|
+
if (enabledPlugins.length > 0) capabilities.unshift('tools')
|
|
83
|
+
return capabilities
|
|
63
84
|
}
|
|
64
85
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
86
|
+
export function buildNoToolsGuidance(): string[] {
|
|
87
|
+
return [
|
|
88
|
+
'## Tool Availability',
|
|
89
|
+
'No session tools are enabled in this chat.',
|
|
90
|
+
'Do not imply that a normal read-only action is waiting on user permission or approval when the real blocker is missing tool access.',
|
|
91
|
+
'If browsing, web fetches, file edits, or other actions are unavailable, state that the capability is not enabled in this session.',
|
|
92
|
+
'Only mention approval when a real runtime tool explicitly returned an approval requirement for a concrete action.',
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function buildEnabledToolsAutonomyGuidance(options?: {
|
|
97
|
+
approvalsEnabled?: boolean
|
|
98
|
+
approvalAutoApproveCategories?: ApprovalCategory[] | null
|
|
99
|
+
}): string[] {
|
|
100
|
+
const lines = [
|
|
101
|
+
'## Tool Autonomy',
|
|
102
|
+
'Enabled session tools are already available for normal use in this chat.',
|
|
103
|
+
'Do not ask the user for permission before using enabled tools for ordinary read-only work, routine diagnostics, or reversible execution steps that are clearly part of the request.',
|
|
104
|
+
'If the user asks you to use an enabled tool or to perform a task that clearly maps to an enabled tool, attempt that tool path before asking the user to do the work manually.',
|
|
105
|
+
'Only surface approval or permission as a blocker when a real runtime tool result explicitly requires approval for a concrete state-changing action.',
|
|
106
|
+
]
|
|
107
|
+
if (options?.approvalsEnabled === false) {
|
|
108
|
+
lines.push('Approvals are disabled platform-wide in this runtime. Do not tell the user that enabled tool use is waiting on approval.')
|
|
109
|
+
}
|
|
110
|
+
const autoApproved = Array.isArray(options?.approvalAutoApproveCategories)
|
|
111
|
+
? options!.approvalAutoApproveCategories.filter((value): value is ApprovalCategory => typeof value === 'string' && value.trim().length > 0)
|
|
112
|
+
: []
|
|
113
|
+
if (autoApproved.length > 0) {
|
|
114
|
+
lines.push(`These approval categories auto-approve in this runtime: ${autoApproved.join(', ')}.`)
|
|
115
|
+
lines.push('If a tool-backed action falls into one of those categories, call the tool and let the runtime auto-approve it instead of asking the user first in prose.')
|
|
116
|
+
}
|
|
117
|
+
return lines
|
|
69
118
|
}
|
|
70
119
|
|
|
71
120
|
interface SessionWithCredentials {
|
|
@@ -105,15 +154,6 @@ export interface ExecuteChatTurnResult {
|
|
|
105
154
|
estimatedCost?: number
|
|
106
155
|
}
|
|
107
156
|
|
|
108
|
-
export function shouldApplySessionFreshnessReset(source: string): boolean {
|
|
109
|
-
return source !== 'eval'
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function shouldPersistInboundUserMessage(internal: boolean, source: string): boolean {
|
|
113
|
-
if (!internal) return true
|
|
114
|
-
return source === 'eval'
|
|
115
|
-
}
|
|
116
|
-
|
|
117
157
|
function extractEventJson(line: string): SSEEvent | null {
|
|
118
158
|
if (!line.startsWith('data: ')) return null
|
|
119
159
|
try {
|
|
@@ -156,25 +196,6 @@ export function collectToolEvent(ev: SSEEvent, bag: MessageToolEvent[]) {
|
|
|
156
196
|
}
|
|
157
197
|
}
|
|
158
198
|
|
|
159
|
-
function escapeRegExp(value: string): string {
|
|
160
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function hasExplicitToolMention(message: string, toolName: string): boolean {
|
|
164
|
-
const escaped = escapeRegExp(toolName)
|
|
165
|
-
const negated = new RegExp(`\\b(?:do not|don't|dont|avoid|skip|without|never)\\s+(?:use\\s+|call\\s+|invoke\\s+)?(?:the\\s+)?\`?${escaped}\`?(?:\\s+tool)?\\b`, 'i')
|
|
166
|
-
if (negated.test(message)) return false
|
|
167
|
-
const boundary = new RegExp(`(^|[^a-z0-9_])\`?${escaped}\`?([^a-z0-9_]|$)`, 'i')
|
|
168
|
-
return boundary.test(message)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function hasExplicitGenericToolRequest(message: string, toolName: string): boolean {
|
|
172
|
-
const escaped = escapeRegExp(toolName)
|
|
173
|
-
const negated = new RegExp(`\\b(?:do not|don't|dont|avoid|skip|without|never)\\s+(?:use\\s+|call\\s+|invoke\\s+)?(?:the\\s+)?${escaped}(?:\\s+tool)?\\b`, 'i')
|
|
174
|
-
if (negated.test(message)) return false
|
|
175
|
-
return new RegExp(`(^|[\\s(])\`${escaped}\`([\\s).,!?]|$)|\\b${escaped}\\s+tool\\b|\\buse\\s+(?:the\\s+)?${escaped}\\b|\\bcall\\s+(?:the\\s+)?${escaped}\\b|\\binvoke\\s+(?:the\\s+)?${escaped}\\b`, 'i').test(message)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
199
|
export function dedupeConsecutiveToolEvents(events: MessageToolEvent[]): MessageToolEvent[] {
|
|
179
200
|
const sameEvent = (left: MessageToolEvent, right: MessageToolEvent): boolean => (
|
|
180
201
|
left.name === right.name
|
|
@@ -231,111 +252,6 @@ export function deriveTerminalRunError(params: {
|
|
|
231
252
|
return undefined
|
|
232
253
|
}
|
|
233
254
|
|
|
234
|
-
function extractDelegateResponse(outputText: string): string | null {
|
|
235
|
-
try {
|
|
236
|
-
const parsed = JSON.parse(outputText) as Record<string, unknown>
|
|
237
|
-
if (typeof parsed.response === 'string' && parsed.response.trim()) return parsed.response.trim()
|
|
238
|
-
if (typeof parsed.result === 'string' && parsed.result.trim()) return parsed.result.trim()
|
|
239
|
-
return null
|
|
240
|
-
} catch {
|
|
241
|
-
return null
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const MANAGE_PLATFORM_RESOURCE_TO_TOOL: Record<string, string> = {
|
|
246
|
-
agent: 'manage_agents',
|
|
247
|
-
agents: 'manage_agents',
|
|
248
|
-
project: 'manage_projects',
|
|
249
|
-
projects: 'manage_projects',
|
|
250
|
-
task: 'manage_tasks',
|
|
251
|
-
tasks: 'manage_tasks',
|
|
252
|
-
schedule: 'manage_schedules',
|
|
253
|
-
schedules: 'manage_schedules',
|
|
254
|
-
skill: 'manage_skills',
|
|
255
|
-
skills: 'manage_skills',
|
|
256
|
-
document: 'manage_documents',
|
|
257
|
-
documents: 'manage_documents',
|
|
258
|
-
secret: 'manage_secrets',
|
|
259
|
-
secrets: 'manage_secrets',
|
|
260
|
-
connector: 'manage_connectors',
|
|
261
|
-
connectors: 'manage_connectors',
|
|
262
|
-
session: 'manage_sessions',
|
|
263
|
-
sessions: 'manage_sessions',
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
export function translateRequestedToolInvocation(
|
|
267
|
-
requestedName: string,
|
|
268
|
-
rawArgs: Record<string, unknown>,
|
|
269
|
-
messageFallback: string,
|
|
270
|
-
availableToolNames?: Iterable<string>,
|
|
271
|
-
): { toolName: string; args: Record<string, unknown> } {
|
|
272
|
-
const available = new Set(availableToolNames || [])
|
|
273
|
-
|
|
274
|
-
if (requestedName === 'web_search') {
|
|
275
|
-
return {
|
|
276
|
-
toolName: 'web',
|
|
277
|
-
args: {
|
|
278
|
-
action: 'search',
|
|
279
|
-
query: typeof rawArgs.query === 'string' ? rawArgs.query : messageFallback.trim(),
|
|
280
|
-
maxResults: typeof rawArgs.maxResults === 'number' ? rawArgs.maxResults : 5,
|
|
281
|
-
},
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
if (requestedName === 'web_fetch') {
|
|
285
|
-
return {
|
|
286
|
-
toolName: 'web',
|
|
287
|
-
args: {
|
|
288
|
-
action: 'fetch',
|
|
289
|
-
url: rawArgs.url,
|
|
290
|
-
},
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
if (requestedName === 'delegate_to_claude_code') {
|
|
294
|
-
return { toolName: 'delegate', args: { ...rawArgs, backend: 'claude' } }
|
|
295
|
-
}
|
|
296
|
-
if (requestedName === 'delegate_to_codex_cli') {
|
|
297
|
-
return { toolName: 'delegate', args: { ...rawArgs, backend: 'codex' } }
|
|
298
|
-
}
|
|
299
|
-
if (requestedName === 'delegate_to_opencode_cli') {
|
|
300
|
-
return { toolName: 'delegate', args: { ...rawArgs, backend: 'opencode' } }
|
|
301
|
-
}
|
|
302
|
-
if (requestedName === 'delegate_to_gemini_cli') {
|
|
303
|
-
return { toolName: 'delegate', args: { ...rawArgs, backend: 'gemini' } }
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const managePrefix = 'manage_'
|
|
307
|
-
if (requestedName === 'manage_platform') {
|
|
308
|
-
const resource = typeof rawArgs.resource === 'string'
|
|
309
|
-
? rawArgs.resource.trim().toLowerCase()
|
|
310
|
-
: ''
|
|
311
|
-
const specificTool = MANAGE_PLATFORM_RESOURCE_TO_TOOL[resource]
|
|
312
|
-
if (specificTool && available.has(specificTool) && !available.has('manage_platform')) {
|
|
313
|
-
return { toolName: specificTool, args: rawArgs }
|
|
314
|
-
}
|
|
315
|
-
return { toolName: requestedName, args: rawArgs }
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
if (requestedName.startsWith(managePrefix) && requestedName !== 'manage_platform') {
|
|
319
|
-
if (!available.has(requestedName) && available.has('manage_platform')) {
|
|
320
|
-
const resource = requestedName.slice(managePrefix.length)
|
|
321
|
-
if (resource) {
|
|
322
|
-
const { action, id, data, ...rest } = rawArgs
|
|
323
|
-
const nextArgs: Record<string, unknown> = { resource, ...rest }
|
|
324
|
-
if (action !== undefined) nextArgs.action = action
|
|
325
|
-
if (id !== undefined) nextArgs.id = id
|
|
326
|
-
if (data !== undefined) nextArgs.data = data
|
|
327
|
-
return {
|
|
328
|
-
toolName: 'manage_platform',
|
|
329
|
-
args: nextArgs,
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
return { toolName: requestedName, args: rawArgs }
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
return { toolName: requestedName, args: rawArgs }
|
|
337
|
-
}
|
|
338
|
-
|
|
339
255
|
export function isLikelyToolErrorOutput(output: string): boolean {
|
|
340
256
|
const trimmed = String(output || '').trim()
|
|
341
257
|
if (!trimmed) return false
|
|
@@ -353,156 +269,10 @@ export function isLikelyToolErrorOutput(output: string): boolean {
|
|
|
353
269
|
return false
|
|
354
270
|
}
|
|
355
271
|
|
|
356
|
-
function normalizeWorkspaceSandboxLinks(text: string, cwd: string): string {
|
|
357
|
-
return text.replace(/\[([^\]]+)\]\(sandbox:\/workspace\/([^)]+)\)/g, (raw, label: string, relativePath: string) => {
|
|
358
|
-
const normalized = String(relativePath || '').replace(/^\/+/, '')
|
|
359
|
-
if (!normalized) return raw
|
|
360
|
-
const resolvedCwd = path.resolve(cwd)
|
|
361
|
-
const resolved = path.resolve(resolvedCwd, normalized)
|
|
362
|
-
if (!resolved.startsWith(resolvedCwd)) return raw
|
|
363
|
-
if (!fs.existsSync(resolved)) return raw
|
|
364
|
-
return `[${label}](/api/files/serve?path=${encodeURIComponent(resolved)})`
|
|
365
|
-
})
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function normalizeAbsoluteFileMarkdownLinks(text: string): string {
|
|
369
|
-
return text.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (raw, label: string, target: string) => {
|
|
370
|
-
if (!path.isAbsolute(target)) return raw
|
|
371
|
-
const resolved = path.resolve(target)
|
|
372
|
-
if (!fs.existsSync(resolved)) return raw
|
|
373
|
-
return `[${label}](/api/files/serve?path=${encodeURIComponent(resolved)})`
|
|
374
|
-
})
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
export function normalizeAssistantArtifactLinks(text: string, cwd: string): string {
|
|
378
|
-
const uploadsNormalized = text.replace(/sandbox:\/api\/uploads\//g, '/api/uploads/')
|
|
379
|
-
const workspaceNormalized = normalizeWorkspaceSandboxLinks(uploadsNormalized, cwd)
|
|
380
|
-
return normalizeAbsoluteFileMarkdownLinks(workspaceNormalized)
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function extractHeartbeatStatus(text: string): { goal?: string; status?: string; summary?: string; nextAction?: string } | null {
|
|
384
|
-
const match = text.match(/\[AGENT_HEARTBEAT_META\]\s*(\{[^\n]*\})/i)
|
|
385
|
-
if (!match) return null
|
|
386
|
-
try {
|
|
387
|
-
const meta = JSON.parse(match[1]) as Record<string, unknown>
|
|
388
|
-
const payload: { goal?: string; status?: string; summary?: string; nextAction?: string } = {}
|
|
389
|
-
if (typeof meta.goal === 'string' && meta.goal.trim()) payload.goal = meta.goal.trim()
|
|
390
|
-
if (typeof meta.status === 'string' && meta.status.trim()) payload.status = meta.status.trim()
|
|
391
|
-
if (typeof meta.summary === 'string' && meta.summary.trim()) payload.summary = meta.summary.trim()
|
|
392
|
-
if (typeof meta.next_action === 'string' && meta.next_action.trim()) payload.nextAction = meta.next_action.trim()
|
|
393
|
-
return Object.keys(payload).length > 0 ? payload : null
|
|
394
|
-
} catch {
|
|
395
|
-
return null
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
function shouldReplaceRecentAssistantMessage(params: {
|
|
400
|
-
previous: Message | null | undefined
|
|
401
|
-
nextToolEvents: MessageToolEvent[]
|
|
402
|
-
nextKind: Message['kind']
|
|
403
|
-
now: number
|
|
404
|
-
}): boolean {
|
|
405
|
-
const { previous, nextToolEvents, nextKind, now } = params
|
|
406
|
-
if (!previous || previous.role !== 'assistant') return false
|
|
407
|
-
if (nextToolEvents.length === 0) return false
|
|
408
|
-
if (previous.kind && nextKind && previous.kind !== nextKind) return false
|
|
409
|
-
if (typeof previous.time === 'number' && now - previous.time > 45_000) return false
|
|
410
|
-
const prevTools = Array.isArray(previous.toolEvents) ? previous.toolEvents.length : 0
|
|
411
|
-
return prevTools === 0
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function hasPersistableAssistantPayload(text: string, thinking: string, toolEvents: MessageToolEvent[]): boolean {
|
|
415
|
-
return text.trim().length > 0 || thinking.trim().length > 0 || toolEvents.length > 0
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
function getPersistedAssistantText(text: string, toolEvents: MessageToolEvent[]): string {
|
|
419
|
-
const trimmed = text.trim()
|
|
420
|
-
if (trimmed) return trimmed
|
|
421
|
-
return buildToolEventAssistantSummary(toolEvents)
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
function getToolEventsSnapshotKey(toolEvents: MessageToolEvent[]): string {
|
|
425
|
-
return JSON.stringify(toolEvents.map((event) => [
|
|
426
|
-
event.name,
|
|
427
|
-
event.input,
|
|
428
|
-
event.output || '',
|
|
429
|
-
event.error === true,
|
|
430
|
-
event.toolCallId || '',
|
|
431
|
-
]))
|
|
432
|
-
}
|
|
433
|
-
|
|
434
272
|
export function pruneSuppressedHeartbeatStreamMessage(messages: Message[]): boolean {
|
|
435
273
|
return pruneStreamingAssistantArtifacts(messages)
|
|
436
274
|
}
|
|
437
275
|
|
|
438
|
-
export function requestedToolNamesFromMessage(message: string): string[] {
|
|
439
|
-
const explicitCandidates = [
|
|
440
|
-
'delegate_to_claude_code',
|
|
441
|
-
'delegate_to_codex_cli',
|
|
442
|
-
'delegate_to_opencode_cli',
|
|
443
|
-
'delegate_to_gemini_cli',
|
|
444
|
-
'connector_message_tool',
|
|
445
|
-
'sessions_tool',
|
|
446
|
-
'whoami_tool',
|
|
447
|
-
'search_history_tool',
|
|
448
|
-
'manage_agents',
|
|
449
|
-
'manage_tasks',
|
|
450
|
-
'manage_schedules',
|
|
451
|
-
'manage_documents',
|
|
452
|
-
'manage_webhooks',
|
|
453
|
-
'manage_skills',
|
|
454
|
-
'manage_connectors',
|
|
455
|
-
'manage_sessions',
|
|
456
|
-
'manage_secrets',
|
|
457
|
-
'manage_capabilities',
|
|
458
|
-
'manage_platform',
|
|
459
|
-
'manage_chatrooms',
|
|
460
|
-
'search_marketplace',
|
|
461
|
-
'monitor_tool',
|
|
462
|
-
'plugin_creator_tool',
|
|
463
|
-
'memory_tool',
|
|
464
|
-
'memory_search',
|
|
465
|
-
'memory_get',
|
|
466
|
-
'memory_store',
|
|
467
|
-
'memory_update',
|
|
468
|
-
'wallet_tool',
|
|
469
|
-
'http_request',
|
|
470
|
-
'send_file',
|
|
471
|
-
'sandbox_exec',
|
|
472
|
-
'sandbox_list_runtimes',
|
|
473
|
-
'schedule_wake',
|
|
474
|
-
'spawn_subagent',
|
|
475
|
-
'ask_human',
|
|
476
|
-
'context_status',
|
|
477
|
-
'context_summarize',
|
|
478
|
-
'openclaw_nodes',
|
|
479
|
-
'openclaw_workspace',
|
|
480
|
-
]
|
|
481
|
-
const genericCandidates = [
|
|
482
|
-
'browser',
|
|
483
|
-
'web',
|
|
484
|
-
'shell',
|
|
485
|
-
'files',
|
|
486
|
-
'edit_file',
|
|
487
|
-
'git',
|
|
488
|
-
'canvas',
|
|
489
|
-
'mailbox',
|
|
490
|
-
'document',
|
|
491
|
-
'extract',
|
|
492
|
-
'table',
|
|
493
|
-
'crawl',
|
|
494
|
-
'email',
|
|
495
|
-
]
|
|
496
|
-
const requested = explicitCandidates.filter((name) => hasExplicitToolMention(message, name))
|
|
497
|
-
for (const name of genericCandidates) {
|
|
498
|
-
if (hasExplicitGenericToolRequest(message, name)) requested.push(name)
|
|
499
|
-
}
|
|
500
|
-
if (hasExplicitGenericToolRequest(message, 'delegate')) {
|
|
501
|
-
requested.push('delegate')
|
|
502
|
-
}
|
|
503
|
-
return Array.from(new Set(requested))
|
|
504
|
-
}
|
|
505
|
-
|
|
506
276
|
function parseToolJsonObject(raw: string): Record<string, unknown> | null {
|
|
507
277
|
const trimmed = raw.trim()
|
|
508
278
|
if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) return null
|
|
@@ -568,216 +338,6 @@ export function reconcileConnectorDeliveryText(text: string, events: MessageTool
|
|
|
568
338
|
return `I couldn't send that through the configured connector. ${failureSummary}`.trim()
|
|
569
339
|
}
|
|
570
340
|
|
|
571
|
-
function parseKeyValueArgs(raw: string): Record<string, string> {
|
|
572
|
-
const out: Record<string, string> = {}
|
|
573
|
-
const regex = /([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*("([^"]*)"|'([^']*)'|[^\s,]+)/g
|
|
574
|
-
let match: RegExpExecArray | null = null
|
|
575
|
-
while ((match = regex.exec(raw)) !== null) {
|
|
576
|
-
const key = match[1]
|
|
577
|
-
const value = match[3] ?? match[4] ?? match[2] ?? ''
|
|
578
|
-
out[key] = value.replace(/^['"]|['"]$/g, '').trim()
|
|
579
|
-
}
|
|
580
|
-
return out
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
function extractConnectorMessageArgs(message: string): {
|
|
584
|
-
action:
|
|
585
|
-
| 'list_running'
|
|
586
|
-
| 'list_targets'
|
|
587
|
-
| 'start'
|
|
588
|
-
| 'stop'
|
|
589
|
-
| 'send'
|
|
590
|
-
| 'send_voice_note'
|
|
591
|
-
| 'schedule_followup'
|
|
592
|
-
platform?: string
|
|
593
|
-
connectorId?: string
|
|
594
|
-
to?: string
|
|
595
|
-
message?: string
|
|
596
|
-
voiceText?: string
|
|
597
|
-
voiceId?: string
|
|
598
|
-
imageUrl?: string
|
|
599
|
-
fileUrl?: string
|
|
600
|
-
mediaPath?: string
|
|
601
|
-
mimeType?: string
|
|
602
|
-
fileName?: string
|
|
603
|
-
caption?: string
|
|
604
|
-
delaySec?: number
|
|
605
|
-
followUpMessage?: string
|
|
606
|
-
followUpDelaySec?: number
|
|
607
|
-
ptt?: boolean
|
|
608
|
-
approved?: boolean
|
|
609
|
-
} | null {
|
|
610
|
-
if (!message.toLowerCase().includes('connector_message_tool')) return null
|
|
611
|
-
const parsed = parseKeyValueArgs(message)
|
|
612
|
-
|
|
613
|
-
let payload = parsed.message
|
|
614
|
-
if (!payload) {
|
|
615
|
-
const quoted = message.match(/message\s*=\s*("(.*?)"|'(.*?)')/i)
|
|
616
|
-
if (quoted) payload = (quoted[2] || quoted[3] || '').trim()
|
|
617
|
-
}
|
|
618
|
-
if (!payload) {
|
|
619
|
-
const raw = message.match(/message\s*=\s*([^\n]+)/i)
|
|
620
|
-
if (raw?.[1]) {
|
|
621
|
-
payload = raw[1]
|
|
622
|
-
.replace(/\b(Return|Output|Then|Respond)\b[\s\S]*$/i, '')
|
|
623
|
-
.trim()
|
|
624
|
-
.replace(/^['"]|['"]$/g, '')
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
const actionRaw = (parsed.action || 'send').toLowerCase()
|
|
629
|
-
const action = (
|
|
630
|
-
actionRaw === 'list_running'
|
|
631
|
-
|| actionRaw === 'list_targets'
|
|
632
|
-
|| actionRaw === 'start'
|
|
633
|
-
|| actionRaw === 'stop'
|
|
634
|
-
|| actionRaw === 'send'
|
|
635
|
-
|| actionRaw === 'send_voice_note'
|
|
636
|
-
|| actionRaw === 'schedule_followup'
|
|
637
|
-
)
|
|
638
|
-
? actionRaw
|
|
639
|
-
: 'send'
|
|
640
|
-
const args: {
|
|
641
|
-
action:
|
|
642
|
-
| 'list_running'
|
|
643
|
-
| 'list_targets'
|
|
644
|
-
| 'start'
|
|
645
|
-
| 'stop'
|
|
646
|
-
| 'send'
|
|
647
|
-
| 'send_voice_note'
|
|
648
|
-
| 'schedule_followup'
|
|
649
|
-
platform?: string
|
|
650
|
-
connectorId?: string
|
|
651
|
-
to?: string
|
|
652
|
-
message?: string
|
|
653
|
-
voiceText?: string
|
|
654
|
-
voiceId?: string
|
|
655
|
-
imageUrl?: string
|
|
656
|
-
fileUrl?: string
|
|
657
|
-
mediaPath?: string
|
|
658
|
-
mimeType?: string
|
|
659
|
-
fileName?: string
|
|
660
|
-
caption?: string
|
|
661
|
-
delaySec?: number
|
|
662
|
-
followUpMessage?: string
|
|
663
|
-
followUpDelaySec?: number
|
|
664
|
-
ptt?: boolean
|
|
665
|
-
approved?: boolean
|
|
666
|
-
} = { action }
|
|
667
|
-
const quoted = (key: string): string | undefined => {
|
|
668
|
-
const m = message.match(new RegExp(`${key}\\s*=\\s*(\"([^\"]*)\"|'([^']*)')`, 'i'))
|
|
669
|
-
return (m?.[2] || m?.[3] || '').trim() || undefined
|
|
670
|
-
}
|
|
671
|
-
if (parsed.platform) args.platform = parsed.platform
|
|
672
|
-
if (parsed.connectorId) args.connectorId = parsed.connectorId
|
|
673
|
-
if (parsed.to) args.to = parsed.to
|
|
674
|
-
if (payload) args.message = payload
|
|
675
|
-
if (parsed.voiceText) args.voiceText = parsed.voiceText
|
|
676
|
-
if (parsed.voiceId) args.voiceId = parsed.voiceId
|
|
677
|
-
args.imageUrl = parsed.imageUrl || quoted('imageUrl')
|
|
678
|
-
args.fileUrl = parsed.fileUrl || quoted('fileUrl')
|
|
679
|
-
args.mediaPath = parsed.mediaPath || quoted('mediaPath')
|
|
680
|
-
args.mimeType = parsed.mimeType || quoted('mimeType')
|
|
681
|
-
args.fileName = parsed.fileName || quoted('fileName')
|
|
682
|
-
args.caption = parsed.caption || quoted('caption')
|
|
683
|
-
if (parsed.followUpMessage) args.followUpMessage = parsed.followUpMessage
|
|
684
|
-
if (parsed.delaySec && Number.isFinite(Number(parsed.delaySec))) args.delaySec = Number(parsed.delaySec)
|
|
685
|
-
if (parsed.followUpDelaySec && Number.isFinite(Number(parsed.followUpDelaySec))) args.followUpDelaySec = Number(parsed.followUpDelaySec)
|
|
686
|
-
if (parsed.ptt) args.ptt = ['true', '1', 'yes', 'on'].includes(parsed.ptt.toLowerCase())
|
|
687
|
-
if (parsed.approved) args.approved = ['true', '1', 'yes', 'on'].includes(parsed.approved.toLowerCase())
|
|
688
|
-
return args
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
function extractDelegationTask(message: string, toolName: string): string | null {
|
|
692
|
-
if (!message.toLowerCase().includes(toolName.toLowerCase())) return null
|
|
693
|
-
const patterns = [
|
|
694
|
-
/task\s+exactly\s*:\s*"([^"]+)"/i,
|
|
695
|
-
/task\s+exactly\s*:\s*'([^']+)'/i,
|
|
696
|
-
/task\s+exactly\s*:\s*([^\n]+?)(?:\.\s|$)/i,
|
|
697
|
-
/task\s*:\s*"([^"]+)"/i,
|
|
698
|
-
/task\s*:\s*'([^']+)'/i,
|
|
699
|
-
/task\s*:\s*([^\n]+?)(?:\.\s|$)/i,
|
|
700
|
-
]
|
|
701
|
-
for (const re of patterns) {
|
|
702
|
-
const m = message.match(re)
|
|
703
|
-
const task = (m?.[1] || '').trim()
|
|
704
|
-
if (task) return task
|
|
705
|
-
}
|
|
706
|
-
return null
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
function hasToolEnabled(session: SessionWithTools, toolName: string): boolean {
|
|
710
|
-
return pluginIdMatches(session?.plugins || session?.tools || [], toolName)
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
export function hasDirectLocalCodingTools(session: SessionWithTools): boolean {
|
|
714
|
-
return [
|
|
715
|
-
'shell',
|
|
716
|
-
'execute_command',
|
|
717
|
-
'files',
|
|
718
|
-
'edit_file',
|
|
719
|
-
'openclaw_workspace',
|
|
720
|
-
'sandbox',
|
|
721
|
-
].some((toolName) => hasToolEnabled(session, toolName))
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
function enabledDelegationTools(session: SessionWithTools): DelegateTool[] {
|
|
725
|
-
const tools: DelegateTool[] = []
|
|
726
|
-
if (hasToolEnabled(session, 'claude_code') || hasToolEnabled(session, 'delegate')) tools.push('delegate_to_claude_code')
|
|
727
|
-
if (hasToolEnabled(session, 'codex_cli')) tools.push('delegate_to_codex_cli')
|
|
728
|
-
if (hasToolEnabled(session, 'opencode_cli')) tools.push('delegate_to_opencode_cli')
|
|
729
|
-
if (hasToolEnabled(session, 'gemini_cli')) tools.push('delegate_to_gemini_cli')
|
|
730
|
-
return tools
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
function parseUsdLimit(value: unknown): number | null {
|
|
734
|
-
const parsed = typeof value === 'number'
|
|
735
|
-
? value
|
|
736
|
-
: typeof value === 'string'
|
|
737
|
-
? Number.parseFloat(value)
|
|
738
|
-
: Number.NaN
|
|
739
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return null
|
|
740
|
-
return Math.max(0.01, Math.min(1_000_000, parsed))
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
function getTodaySpendUsd(): number {
|
|
744
|
-
const usage = loadUsage()
|
|
745
|
-
const dayStart = new Date()
|
|
746
|
-
dayStart.setHours(0, 0, 0, 0)
|
|
747
|
-
const minTs = dayStart.getTime()
|
|
748
|
-
let total = 0
|
|
749
|
-
for (const records of Object.values(usage)) {
|
|
750
|
-
for (const record of records || []) {
|
|
751
|
-
const rec = record as Record<string, unknown>
|
|
752
|
-
const ts = typeof rec?.timestamp === 'number' ? rec.timestamp : 0
|
|
753
|
-
if (ts < minTs) continue
|
|
754
|
-
const cost = typeof rec?.estimatedCost === 'number' ? rec.estimatedCost : 0
|
|
755
|
-
if (Number.isFinite(cost) && cost > 0) total += cost
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
return total
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
function findFirstUrl(text: string): string | null {
|
|
762
|
-
const m = text.match(/https?:\/\/[^\s<>"')]+/i)
|
|
763
|
-
return m?.[0] || null
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
function isMemoryListIntent(message: string): boolean {
|
|
767
|
-
const text = message.toLowerCase()
|
|
768
|
-
if (!/\bmemory|memories|remember\b/.test(text)) return false
|
|
769
|
-
if (/\b(save|store|memorize|add to memory|write to memory|remember this)\b/.test(text)) return false
|
|
770
|
-
if (/\bmemory_tool\b/.test(text)) return true
|
|
771
|
-
return (
|
|
772
|
-
/\blist\b[\s\w]{0,24}\bmemories\b/.test(text)
|
|
773
|
-
|| /\bshow\b[\s\w]{0,24}\bmemories\b/.test(text)
|
|
774
|
-
|| /\bget\b[\s\w]{0,24}\bmemories\b/.test(text)
|
|
775
|
-
|| /\bwhat\b[\s\w]{0,40}\bmemories\b/.test(text)
|
|
776
|
-
|| /\bwhat do you remember\b/.test(text)
|
|
777
|
-
|| /\brecall\b[\s\w]{0,24}\bmemories?\b/.test(text)
|
|
778
|
-
)
|
|
779
|
-
}
|
|
780
|
-
|
|
781
341
|
function syncSessionFromAgent(sessionId: string): void {
|
|
782
342
|
const sessions = loadSessions()
|
|
783
343
|
const session = sessions[sessionId]
|
|
@@ -886,6 +446,7 @@ function buildAgentSystemPrompt(session: Session): string | undefined {
|
|
|
886
446
|
|
|
887
447
|
const settings = loadSettings()
|
|
888
448
|
const parts: string[] = []
|
|
449
|
+
const enabledPlugins = Array.isArray(session.plugins) ? session.plugins : (Array.isArray(agent.plugins) ? agent.plugins : [])
|
|
889
450
|
|
|
890
451
|
// 1. Identity & Persona (Grounded OpenClaw Style)
|
|
891
452
|
const identityLines = [`## My Identity`]
|
|
@@ -904,7 +465,9 @@ function buildAgentSystemPrompt(session: Session): string | undefined {
|
|
|
904
465
|
const runtimeLines = [
|
|
905
466
|
'## Runtime',
|
|
906
467
|
`os=${process.platform} | host=${os.hostname()} | agent=${agent.id} | provider=${agent.provider} | model=${agent.model}`,
|
|
907
|
-
`capabilities
|
|
468
|
+
`capabilities=${buildAgentRuntimeCapabilities(enabledPlugins).join(',')}`,
|
|
469
|
+
`approvals=${settings.approvalsEnabled === false ? 'disabled' : 'enabled_or_tool_specific'}`,
|
|
470
|
+
`approval_auto_approve_categories=${Array.isArray(settings.approvalAutoApproveCategories) && settings.approvalAutoApproveCategories.length > 0 ? settings.approvalAutoApproveCategories.join(',') : 'none'}`,
|
|
908
471
|
]
|
|
909
472
|
parts.push(runtimeLines.join('\n'))
|
|
910
473
|
|
|
@@ -927,7 +490,6 @@ function buildAgentSystemPrompt(session: Session): string | undefined {
|
|
|
927
490
|
|
|
928
491
|
// 5b. Workspace context files (HEARTBEAT.md, IDENTITY.md, AGENTS.md, etc.)
|
|
929
492
|
try {
|
|
930
|
-
const { buildWorkspaceContext } = require('./workspace-context')
|
|
931
493
|
const wsCtx = buildWorkspaceContext({ cwd: session.cwd })
|
|
932
494
|
if (wsCtx.block) parts.push(wsCtx.block)
|
|
933
495
|
} catch {
|
|
@@ -943,7 +505,14 @@ function buildAgentSystemPrompt(session: Session): string | undefined {
|
|
|
943
505
|
]
|
|
944
506
|
parts.push(thinkingHint.join('\n'))
|
|
945
507
|
|
|
946
|
-
|
|
508
|
+
if (enabledPlugins.length === 0) {
|
|
509
|
+
parts.push(buildNoToolsGuidance().join('\n'))
|
|
510
|
+
} else {
|
|
511
|
+
parts.push(buildEnabledToolsAutonomyGuidance({
|
|
512
|
+
approvalsEnabled: settings.approvalsEnabled,
|
|
513
|
+
approvalAutoApproveCategories: settings.approvalAutoApproveCategories,
|
|
514
|
+
}).join('\n'))
|
|
515
|
+
}
|
|
947
516
|
const toolDisciplineLines = buildToolDisciplineLines(enabledPlugins)
|
|
948
517
|
if (toolDisciplineLines.length > 0) parts.push(['## Tool Discipline', ...toolDisciplineLines].join('\n'))
|
|
949
518
|
const operatingGuidance = getPluginManager().collectOperatingGuidance(enabledPlugins)
|
|
@@ -978,42 +547,6 @@ function resolveApiKeyForSession(session: SessionWithCredentials, provider: Prov
|
|
|
978
547
|
return null
|
|
979
548
|
}
|
|
980
549
|
|
|
981
|
-
function stripMarkupForHeartbeat(text: string): string {
|
|
982
|
-
return text
|
|
983
|
-
.replace(/<[^>]*>/g, ' ') // strip HTML tags
|
|
984
|
-
.replace(/ /gi, ' ') // decode nbsp
|
|
985
|
-
.replace(/^[*`~_]+/, '') // strip leading markdown
|
|
986
|
-
.replace(/[*`~_]+$/, '') // strip trailing markdown
|
|
987
|
-
.trim()
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
const HEARTBEAT_OK_RE = /HEARTBEAT_OK[^\w]{0,4}$/
|
|
991
|
-
const NO_MESSAGE_RE = /NO_MESSAGE[^\w]{0,4}$/
|
|
992
|
-
|
|
993
|
-
function classifyHeartbeatResponse(text: string, ackMaxChars: number, hadToolCalls: boolean): 'suppress' | 'strip' | 'keep' {
|
|
994
|
-
const cleaned = stripMarkupForHeartbeat(text)
|
|
995
|
-
if (cleaned === 'HEARTBEAT_OK' || cleaned === 'NO_MESSAGE') return 'suppress'
|
|
996
|
-
if (HEARTBEAT_OK_RE.test(cleaned) || NO_MESSAGE_RE.test(cleaned)) return 'suppress'
|
|
997
|
-
const stripped = cleaned.replace(/HEARTBEAT_OK/gi, '').replace(/NO_MESSAGE/gi, '').trim()
|
|
998
|
-
if (!stripped) return 'suppress'
|
|
999
|
-
if (!hadToolCalls && stripped.length <= ackMaxChars) return 'suppress'
|
|
1000
|
-
return stripped.length < cleaned.length ? 'strip' : 'keep'
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
function estimateConversationTone(text: string): string {
|
|
1004
|
-
const t = text || ''
|
|
1005
|
-
// Technical: code blocks, function signatures, technical terms
|
|
1006
|
-
if (/```/.test(t) || /\b(function|const|let|var|import|export|class|interface|async|await|return)\b/.test(t)) return 'technical'
|
|
1007
|
-
if (/\b(error|bug|debug|stack trace|exception|null|undefined|TypeError)\b/i.test(t)) return 'technical'
|
|
1008
|
-
// Empathetic: emotional/supportive language
|
|
1009
|
-
if (/\b(understand|feel|sorry|empathize|appreciate|grateful|tough|difficult|challenging)\b/i.test(t)) return 'empathetic'
|
|
1010
|
-
// Formal: academic/business language
|
|
1011
|
-
if (/\b(furthermore|regarding|consequently|therefore|henceforth|pursuant|accordingly|notwithstanding)\b/i.test(t)) return 'formal'
|
|
1012
|
-
// Casual: contractions, exclamations, informal language
|
|
1013
|
-
if (/\b(gonna|wanna|gotta|yeah|hey|awesome|cool|lol|btw|tbh)\b/i.test(t) || /!{2,}/.test(t)) return 'casual'
|
|
1014
|
-
return 'neutral'
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
550
|
|
|
1018
551
|
export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promise<ExecuteChatTurnResult> {
|
|
1019
552
|
const { message } = input
|
|
@@ -1029,6 +562,12 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1029
562
|
signal,
|
|
1030
563
|
} = input
|
|
1031
564
|
|
|
565
|
+
// Resolve image path early: if the filesystem path is gone, fall back to
|
|
566
|
+
// the upload URL which resolveImagePath maps back to the uploads directory.
|
|
567
|
+
const resolvedImagePath = resolveImagePath(imagePath, imageUrl) ?? undefined
|
|
568
|
+
|
|
569
|
+
const endTurnPerf = perf.start('chat-execution', 'executeSessionChatTurn', { sessionId, source })
|
|
570
|
+
|
|
1032
571
|
syncSessionFromAgent(sessionId)
|
|
1033
572
|
|
|
1034
573
|
const sessions = loadSessions()
|
|
@@ -1266,9 +805,19 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1266
805
|
let lastPartialSaveAt = 0
|
|
1267
806
|
let lastPartialSnapshotKey = ''
|
|
1268
807
|
let partialSaveTimeout: ReturnType<typeof setTimeout> | null = null
|
|
808
|
+
let partialPersistenceClosed = false
|
|
809
|
+
|
|
810
|
+
const stopPartialAssistantPersistence = () => {
|
|
811
|
+
partialPersistenceClosed = true
|
|
812
|
+
if (partialSaveTimeout) {
|
|
813
|
+
clearTimeout(partialSaveTimeout)
|
|
814
|
+
partialSaveTimeout = null
|
|
815
|
+
}
|
|
816
|
+
}
|
|
1269
817
|
|
|
1270
818
|
const persistStreamingAssistantArtifact = () => {
|
|
1271
819
|
partialSaveTimeout = null
|
|
820
|
+
if (partialPersistenceClosed) return
|
|
1272
821
|
const persistedToolEvents = toolEvents.length ? dedupeConsecutiveToolEvents([...toolEvents]) : []
|
|
1273
822
|
if (!hasPersistableAssistantPayload(streamingPartialText, thinkingText, persistedToolEvents)) return
|
|
1274
823
|
|
|
@@ -1306,6 +855,7 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1306
855
|
}
|
|
1307
856
|
|
|
1308
857
|
const queuePartialAssistantPersist = (immediate = false) => {
|
|
858
|
+
if (partialPersistenceClosed) return
|
|
1309
859
|
const now = Date.now()
|
|
1310
860
|
const minIntervalMs = 400
|
|
1311
861
|
if (immediate || now - lastPartialSaveAt >= minIntervalMs) {
|
|
@@ -1398,12 +948,21 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1398
948
|
let responseCacheHit = false
|
|
1399
949
|
let responseCacheInput: LlmResponseCacheKeyInput | null = null
|
|
1400
950
|
const useLocalOpenClawNativeRuntime = providerType === 'openclaw' && isLocalOpenClawEndpoint(sessionForRun.apiEndpoint)
|
|
1401
|
-
const
|
|
951
|
+
const enabledSessionPlugins = Array.isArray(sessionForRun.plugins)
|
|
952
|
+
? sessionForRun.plugins
|
|
953
|
+
: (Array.isArray(sessionForRun.tools) ? sessionForRun.tools : [])
|
|
954
|
+
const hasPlugins = enabledSessionPlugins.length > 0
|
|
1402
955
|
&& !NON_LANGGRAPH_PROVIDER_IDS.has(providerType)
|
|
1403
956
|
&& !useLocalOpenClawNativeRuntime
|
|
1404
957
|
|
|
1405
958
|
let durationMs = 0
|
|
1406
959
|
const startTs = Date.now()
|
|
960
|
+
const endLlmPerf = perf.start('chat-execution', 'llm-round-trip', {
|
|
961
|
+
sessionId,
|
|
962
|
+
provider: providerType,
|
|
963
|
+
hasPlugins,
|
|
964
|
+
pluginCount: enabledSessionPlugins.length,
|
|
965
|
+
})
|
|
1407
966
|
try {
|
|
1408
967
|
// Heartbeat runs get a small tail of recent messages so the agent can see
|
|
1409
968
|
// prior findings and avoid repeating the same searches. Full history is
|
|
@@ -1412,12 +971,12 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1412
971
|
? getSessionMessages(sessionId).slice(-6)
|
|
1413
972
|
: undefined
|
|
1414
973
|
|
|
1415
|
-
console.log(`[chat-execution] provider=${providerType}, hasPlugins=${hasPlugins}, localOpenClawNative=${useLocalOpenClawNativeRuntime}, imagePath=${
|
|
974
|
+
console.log(`[chat-execution] provider=${providerType}, hasPlugins=${hasPlugins}, localOpenClawNative=${useLocalOpenClawNativeRuntime}, imagePath=${resolvedImagePath || 'none'}, attachedFiles=${attachedFiles?.length || 0}, plugins=${enabledSessionPlugins.length}`)
|
|
1416
975
|
if (hasPlugins) {
|
|
1417
976
|
const result = await streamAgentChat({
|
|
1418
977
|
session: sessionForRun,
|
|
1419
978
|
message: effectiveMessage,
|
|
1420
|
-
imagePath,
|
|
979
|
+
imagePath: resolvedImagePath,
|
|
1421
980
|
attachedFiles,
|
|
1422
981
|
apiKey,
|
|
1423
982
|
systemPrompt,
|
|
@@ -1464,7 +1023,7 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1464
1023
|
fullResponse = await provider.handler.streamChat({
|
|
1465
1024
|
session: sessionForRun,
|
|
1466
1025
|
message: effectiveMessage,
|
|
1467
|
-
imagePath,
|
|
1026
|
+
imagePath: resolvedImagePath,
|
|
1468
1027
|
apiKey,
|
|
1469
1028
|
systemPrompt,
|
|
1470
1029
|
write: (raw: string) => parseAndEmit(raw),
|
|
@@ -1484,8 +1043,10 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1484
1043
|
}
|
|
1485
1044
|
}
|
|
1486
1045
|
durationMs = Date.now() - startTs
|
|
1046
|
+
endLlmPerf({ durationMs, cacheHit: responseCacheHit })
|
|
1487
1047
|
} catch (err: unknown) {
|
|
1488
|
-
|
|
1048
|
+
endLlmPerf({ error: true })
|
|
1049
|
+
errorMessage = toErrorMessage(err)
|
|
1489
1050
|
const failureText = errorMessage || 'Run failed.'
|
|
1490
1051
|
markProviderFailure(providerType, failureText)
|
|
1491
1052
|
emit({ t: 'err', text: failureText })
|
|
@@ -1497,7 +1058,7 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1497
1058
|
})
|
|
1498
1059
|
} finally {
|
|
1499
1060
|
clearInterval(partialSaveTimer)
|
|
1500
|
-
|
|
1061
|
+
stopPartialAssistantPersistence()
|
|
1501
1062
|
active.delete(sessionId)
|
|
1502
1063
|
if (signal) signal.removeEventListener('abort', abortFromOutside)
|
|
1503
1064
|
}
|
|
@@ -1535,197 +1096,32 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1535
1096
|
}
|
|
1536
1097
|
}
|
|
1537
1098
|
|
|
1538
|
-
const
|
|
1539
|
-
|
|
1540
|
-
:
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
sessionId,
|
|
1565
|
-
platformAssignScope: agent?.platformAssignScope || 'self',
|
|
1566
|
-
mcpServerIds: agent?.mcpServerIds,
|
|
1567
|
-
mcpDisabledTools: agent?.mcpDisabledTools,
|
|
1568
|
-
projectId: activeProjectContext.projectId,
|
|
1569
|
-
projectRoot: activeProjectContext.projectRoot,
|
|
1570
|
-
projectName: activeProjectContext.project?.name || null,
|
|
1571
|
-
projectDescription: activeProjectContext.project?.description || null,
|
|
1572
|
-
memoryScopeMode: (((session as unknown as Record<string, unknown>).memoryScopeMode as string | null | undefined) ?? agent?.memoryScopeMode ?? null),
|
|
1573
|
-
})
|
|
1574
|
-
try {
|
|
1575
|
-
const directTool = tools.find((t) => t?.name === toolName) as StructuredToolInterface | undefined
|
|
1576
|
-
const availableToolNames = tools.map((candidate) => candidate?.name).filter(Boolean)
|
|
1577
|
-
const translated = directTool
|
|
1578
|
-
? { toolName, args }
|
|
1579
|
-
: translateRequestedToolInvocation(toolName, args, message, availableToolNames)
|
|
1580
|
-
const selectedTool = directTool || tools.find((t) => t?.name === translated.toolName) as StructuredToolInterface | undefined
|
|
1581
|
-
if (!selectedTool?.invoke) return false
|
|
1582
|
-
const toolInput = JSON.stringify(translated.args)
|
|
1583
|
-
const toolCallId = genId()
|
|
1584
|
-
emit({ t: 'tool_call', toolName, toolInput, toolCallId })
|
|
1585
|
-
const toolOutput = await selectedTool.invoke(translated.args)
|
|
1586
|
-
const outputText = typeof toolOutput === 'string' ? toolOutput : JSON.stringify(toolOutput)
|
|
1587
|
-
emit({ t: 'tool_result', toolName, toolOutput: outputText, toolCallId })
|
|
1588
|
-
const delegateResponse = (
|
|
1589
|
-
toolName === 'delegate'
|
|
1590
|
-
|| toolName.startsWith('delegate_to_')
|
|
1591
|
-
) ? extractDelegateResponse(outputText) : null
|
|
1592
|
-
if (delegateResponse) {
|
|
1593
|
-
fullResponse = delegateResponse
|
|
1594
|
-
} else if (!fullResponse.trim() && outputText?.trim()) {
|
|
1595
|
-
// Don't overwrite fullResponse with raw tool output — it's already captured
|
|
1596
|
-
// in toolEvents. Only set a brief notice when the LLM produced no text,
|
|
1597
|
-
// so the message bubble isn't empty.
|
|
1598
|
-
const label = toolName.replace(/_/g, ' ')
|
|
1599
|
-
fullResponse = `Used **${label}** — see tool output above for details.`
|
|
1600
|
-
}
|
|
1601
|
-
calledNames.add(toolName)
|
|
1602
|
-
return true
|
|
1603
|
-
} catch (forceErr: unknown) {
|
|
1604
|
-
emit({ t: 'err', text: `${failurePrefix}: ${forceErr instanceof Error ? forceErr.message : String(forceErr)}` })
|
|
1605
|
-
return false
|
|
1606
|
-
} finally {
|
|
1607
|
-
await cleanup()
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
|
-
if (requestedToolNames.includes('connector_message_tool') && !calledNames.has('connector_message_tool')) {
|
|
1612
|
-
const forcedArgs = extractConnectorMessageArgs(message)
|
|
1613
|
-
if (forcedArgs) {
|
|
1614
|
-
await invokeSessionTool(
|
|
1615
|
-
'connector_message_tool',
|
|
1616
|
-
forcedArgs as unknown as Record<string, unknown>,
|
|
1617
|
-
'Forced connector_message_tool invocation failed',
|
|
1618
|
-
)
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
const forcedDelegationTools: DelegateTool[] = [
|
|
1623
|
-
'delegate_to_claude_code',
|
|
1624
|
-
'delegate_to_codex_cli',
|
|
1625
|
-
'delegate_to_opencode_cli',
|
|
1626
|
-
'delegate_to_gemini_cli',
|
|
1627
|
-
]
|
|
1628
|
-
for (const toolName of forcedDelegationTools) {
|
|
1629
|
-
if (!requestedToolNames.includes(toolName)) continue
|
|
1630
|
-
if (calledNames.has(toolName)) continue
|
|
1631
|
-
const task = extractDelegationTask(message, toolName)
|
|
1632
|
-
if (!task) continue
|
|
1633
|
-
await invokeSessionTool(toolName, { task }, `Forced ${toolName} invocation failed`)
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
const hasDelegationCall = forcedDelegationTools.some((toolName) => calledNames.has(toolName))
|
|
1637
|
-
const enabledDelegateTools = enabledDelegationTools(sessionForRun)
|
|
1638
|
-
const shouldAutoDelegateCoding = (!internal && source === 'chat')
|
|
1639
|
-
&& enabledDelegateTools.length > 0
|
|
1640
|
-
&& !hasDelegationCall
|
|
1641
|
-
&& calledNames.size === 0
|
|
1642
|
-
&& !requestedToolNames.length
|
|
1643
|
-
&& !hasDirectLocalCodingTools(sessionForRun)
|
|
1644
|
-
&& routingDecision?.intent === 'coding'
|
|
1645
|
-
|
|
1646
|
-
if (shouldAutoDelegateCoding) {
|
|
1647
|
-
const baseDelegationOrder = routingDecision?.preferredDelegates?.length
|
|
1648
|
-
? routingDecision.preferredDelegates
|
|
1649
|
-
: forcedDelegationTools
|
|
1650
|
-
const delegationOrder = rankDelegatesByHealth(baseDelegationOrder as DelegateTool[])
|
|
1651
|
-
.filter((tool) => enabledDelegateTools.includes(tool))
|
|
1652
|
-
for (const delegateTool of delegationOrder) {
|
|
1653
|
-
const invoked = await invokeSessionTool(delegateTool, { task: effectiveMessage.trim() }, 'Auto-delegation failed')
|
|
1654
|
-
if (invoked) break
|
|
1655
|
-
}
|
|
1656
|
-
}
|
|
1657
|
-
|
|
1658
|
-
const shouldFailoverDelegate = (!internal && source === 'chat')
|
|
1659
|
-
&& !!errorMessage
|
|
1660
|
-
&& !(fullResponse || '').trim()
|
|
1661
|
-
&& enabledDelegateTools.length > 0
|
|
1662
|
-
&& !hasDelegationCall
|
|
1663
|
-
&& (routingDecision?.intent === 'coding' || routingDecision?.intent === 'general')
|
|
1664
|
-
if (shouldFailoverDelegate) {
|
|
1665
|
-
const preferred = routingDecision?.preferredDelegates?.length
|
|
1666
|
-
? routingDecision.preferredDelegates
|
|
1667
|
-
: forcedDelegationTools
|
|
1668
|
-
const fallbackOrder = rankDelegatesByHealth(preferred as DelegateTool[])
|
|
1669
|
-
.filter((tool) => enabledDelegateTools.includes(tool))
|
|
1670
|
-
for (const delegateTool of fallbackOrder) {
|
|
1671
|
-
const invoked = await invokeSessionTool(
|
|
1672
|
-
delegateTool,
|
|
1673
|
-
{ task: effectiveMessage.trim() },
|
|
1674
|
-
`Provider failover via ${delegateTool} failed`,
|
|
1675
|
-
)
|
|
1676
|
-
if (invoked) {
|
|
1677
|
-
errorMessage = undefined
|
|
1678
|
-
break
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
|
|
1683
|
-
const canAutoRouteWithTools = (!internal && source === 'chat')
|
|
1684
|
-
&& !!routingDecision
|
|
1685
|
-
&& calledNames.size === 0
|
|
1686
|
-
&& requestedToolNames.length === 0
|
|
1687
|
-
|
|
1688
|
-
if (canAutoRouteWithTools && routingDecision?.intent === 'browsing' && routingDecision.primaryUrl && hasToolEnabled(sessionForRun, 'browser')) {
|
|
1689
|
-
await invokeSessionTool(
|
|
1690
|
-
'browser',
|
|
1691
|
-
{ action: 'read_page', url: routingDecision.primaryUrl },
|
|
1692
|
-
'Auto browser routing failed',
|
|
1693
|
-
)
|
|
1694
|
-
}
|
|
1695
|
-
|
|
1696
|
-
if (canAutoRouteWithTools && routingDecision?.intent === 'research') {
|
|
1697
|
-
const routeUrl = routingDecision.primaryUrl || findFirstUrl(message)
|
|
1698
|
-
if (routeUrl && hasToolEnabled(sessionForRun, 'web_fetch')) {
|
|
1699
|
-
await invokeSessionTool('web_fetch', { url: routeUrl }, 'Auto web_fetch routing failed')
|
|
1700
|
-
} else if (hasToolEnabled(sessionForRun, 'web_search')) {
|
|
1701
|
-
await invokeSessionTool('web_search', { query: effectiveMessage.trim(), maxResults: 5 }, 'Auto web_search routing failed')
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
if (
|
|
1706
|
-
canAutoRouteWithTools
|
|
1707
|
-
&& calledNames.size === 0
|
|
1708
|
-
&& hasToolEnabled(sessionForRun, 'memory')
|
|
1709
|
-
&& isMemoryListIntent(message)
|
|
1710
|
-
) {
|
|
1711
|
-
await invokeSessionTool(
|
|
1712
|
-
'memory_tool',
|
|
1713
|
-
{ action: 'list', key: '', scope: 'auto' },
|
|
1714
|
-
'Auto memory listing failed',
|
|
1715
|
-
)
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
if (requestedToolNames.length > 0) {
|
|
1719
|
-
const missed = requestedToolNames.filter((name) => !calledNames.has(name))
|
|
1720
|
-
if (missed.length > 0) {
|
|
1721
|
-
const notice = `Tool execution notice: requested tool(s) ${missed.join(', ')} were not actually invoked in this run.`
|
|
1722
|
-
emit({ t: 'err', text: notice })
|
|
1723
|
-
if (!fullResponse.includes('Tool execution notice:')) {
|
|
1724
|
-
const trimmedResponse = (fullResponse || '').trim()
|
|
1725
|
-
fullResponse = trimmedResponse
|
|
1726
|
-
? `${trimmedResponse}\n\n${notice}`
|
|
1727
|
-
: notice
|
|
1728
|
-
}
|
|
1099
|
+
const endPostProcessPerf = perf.start('chat-execution', 'post-process', { sessionId })
|
|
1100
|
+
const toolRoutingResult = await runPostLlmToolRouting({
|
|
1101
|
+
session: sessionForRun,
|
|
1102
|
+
sessionId,
|
|
1103
|
+
message,
|
|
1104
|
+
effectiveMessage,
|
|
1105
|
+
enabledPlugins: pluginsForRun,
|
|
1106
|
+
toolPolicy,
|
|
1107
|
+
appSettings,
|
|
1108
|
+
internal,
|
|
1109
|
+
source,
|
|
1110
|
+
toolEvents,
|
|
1111
|
+
emit,
|
|
1112
|
+
}, fullResponse, errorMessage)
|
|
1113
|
+
|
|
1114
|
+
fullResponse = toolRoutingResult.fullResponse
|
|
1115
|
+
errorMessage = toolRoutingResult.errorMessage
|
|
1116
|
+
|
|
1117
|
+
if (toolRoutingResult.missedRequestedTools.length > 0) {
|
|
1118
|
+
const notice = `Tool execution notice: requested tool(s) ${toolRoutingResult.missedRequestedTools.join(', ')} were not actually invoked in this run.`
|
|
1119
|
+
emit({ t: 'err', text: notice })
|
|
1120
|
+
if (!fullResponse.includes('Tool execution notice:')) {
|
|
1121
|
+
const trimmedResponse = (fullResponse || '').trim()
|
|
1122
|
+
fullResponse = trimmedResponse
|
|
1123
|
+
? `${trimmedResponse}\n\n${notice}`
|
|
1124
|
+
: notice
|
|
1729
1125
|
}
|
|
1730
1126
|
}
|
|
1731
1127
|
|
|
@@ -1955,6 +1351,15 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1955
1351
|
notify(`messages:${sessionId}`)
|
|
1956
1352
|
}
|
|
1957
1353
|
|
|
1354
|
+
endPostProcessPerf({ toolEventCount: persistedToolEvents.length })
|
|
1355
|
+
endTurnPerf({
|
|
1356
|
+
durationMs,
|
|
1357
|
+
toolEventCount: persistedToolEvents.length,
|
|
1358
|
+
inputTokens: accumulatedUsage.inputTokens || 0,
|
|
1359
|
+
outputTokens: accumulatedUsage.outputTokens || 0,
|
|
1360
|
+
error: !!errorMessage,
|
|
1361
|
+
})
|
|
1362
|
+
|
|
1958
1363
|
return {
|
|
1959
1364
|
runId,
|
|
1960
1365
|
sessionId,
|