@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
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import type { ToolBuildContext } from './context'
|
|
13
13
|
import { safePath } from './context'
|
|
14
14
|
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
15
|
+
import { dedup, errorMessage } from '@/lib/shared-utils'
|
|
15
16
|
|
|
16
17
|
interface TableCondition {
|
|
17
18
|
column: string
|
|
@@ -278,14 +279,14 @@ function groupTable(table: StructuredTable, by: string[], metrics: GroupMetric[]
|
|
|
278
279
|
next[name] = numeric.length ? Math.max(...numeric) : null
|
|
279
280
|
break
|
|
280
281
|
case 'values':
|
|
281
|
-
next[name] =
|
|
282
|
+
next[name] = dedup(values.map((value) => scalarToString(value)).filter(Boolean))
|
|
282
283
|
break
|
|
283
284
|
}
|
|
284
285
|
}
|
|
285
286
|
return next
|
|
286
287
|
})
|
|
287
288
|
|
|
288
|
-
const headers =
|
|
289
|
+
const headers = dedup([...by, ...metrics.map((metric) => metric.as || (metric.column ? `${metric.op}_${metric.column}` : metric.op))])
|
|
289
290
|
return {
|
|
290
291
|
name: `${table.name || 'table'}_grouped`,
|
|
291
292
|
headers,
|
|
@@ -342,7 +343,7 @@ function joinTables(
|
|
|
342
343
|
|
|
343
344
|
return {
|
|
344
345
|
name: `${left.name || 'left'}_joined_${right.name || 'right'}`,
|
|
345
|
-
headers:
|
|
346
|
+
headers: dedup([...left.headers, ...rightHeaders]),
|
|
346
347
|
rows,
|
|
347
348
|
rowCount: rows.length,
|
|
348
349
|
}
|
|
@@ -355,7 +356,7 @@ function pivotTable(
|
|
|
355
356
|
valueField: string,
|
|
356
357
|
aggregate: 'count' | 'sum' | 'first',
|
|
357
358
|
): StructuredTable {
|
|
358
|
-
const columnValues =
|
|
359
|
+
const columnValues = dedup(table.rows.map((row) => scalarToString(row[columnField])).filter(Boolean))
|
|
359
360
|
const grouped = new Map<string, Record<string, unknown>[]>()
|
|
360
361
|
for (const row of table.rows) {
|
|
361
362
|
const key = JSON.stringify(indexColumns.map((column) => row[column] ?? null))
|
|
@@ -506,7 +507,7 @@ async function executeTableAction(args: Record<string, unknown>, bctx: { cwd: st
|
|
|
506
507
|
const persisted = await maybePersistOutput(normalized, bctx.cwd, table)
|
|
507
508
|
return JSON.stringify({ action, ...previewTable(table), output: persisted })
|
|
508
509
|
} catch (err: unknown) {
|
|
509
|
-
return `Error: ${
|
|
510
|
+
return `Error: ${errorMessage(err)}`
|
|
510
511
|
}
|
|
511
512
|
}
|
|
512
513
|
|
|
@@ -122,7 +122,7 @@ describe('wallet tool generic execution', () => {
|
|
|
122
122
|
assert.equal(result.action, 'sign_message')
|
|
123
123
|
|
|
124
124
|
const approvals = storage.loadApprovals()
|
|
125
|
-
const pending = Object.values(approvals).find((approval) => approval.category === 'wallet_action')
|
|
125
|
+
const pending: any = Object.values(approvals).find((approval: any) => approval.category === 'wallet_action')
|
|
126
126
|
assert.ok(pending)
|
|
127
127
|
assert.equal(pending?.status, 'pending')
|
|
128
128
|
})
|
|
@@ -147,7 +147,7 @@ describe('wallet tool generic execution', () => {
|
|
|
147
147
|
assert.equal(approvalRequest.type, 'plugin_wallet_action_request')
|
|
148
148
|
|
|
149
149
|
const approvals = storage.loadApprovals()
|
|
150
|
-
const pending = approvalRequest.approvalId ? approvals[approvalRequest.approvalId] : undefined
|
|
150
|
+
const pending: any = approvalRequest.approvalId ? approvals[approvalRequest.approvalId] : undefined
|
|
151
151
|
assert.ok(pending)
|
|
152
152
|
storage.upsertApproval(pending!.id, {
|
|
153
153
|
...pending,
|
|
@@ -243,7 +243,7 @@ describe('wallet tool generic execution', () => {
|
|
|
243
243
|
assert.equal(approvalRequest.action, 'swap')
|
|
244
244
|
|
|
245
245
|
const approvals = storage.loadApprovals()
|
|
246
|
-
const pending = approvalRequest.approvalId ? approvals[approvalRequest.approvalId] : undefined
|
|
246
|
+
const pending: any = approvalRequest.approvalId ? approvals[approvalRequest.approvalId] : undefined
|
|
247
247
|
assert.ok(pending)
|
|
248
248
|
assert.equal(pending?.status, 'pending')
|
|
249
249
|
assert.equal(String(pending?.data.amountAtomic), '1000000')
|
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
getWalletsByAgentId,
|
|
50
50
|
isValidWalletAddress,
|
|
51
51
|
} from '../wallet-service'
|
|
52
|
+
import { errorMessage } from '@/lib/shared-utils'
|
|
52
53
|
|
|
53
54
|
const WALLET_TOOL_ACTIONS = [
|
|
54
55
|
'setup',
|
|
@@ -81,7 +82,7 @@ function parseJsonValue<T>(value: unknown, label: string): T | undefined {
|
|
|
81
82
|
try {
|
|
82
83
|
return JSON.parse(trimmed) as T
|
|
83
84
|
} catch (err: unknown) {
|
|
84
|
-
throw new Error(`${label} must be valid JSON: ${
|
|
85
|
+
throw new Error(`${label} must be valid JSON: ${errorMessage(err)}`)
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
return value as T
|
|
@@ -423,7 +424,7 @@ async function executeWalletAction(args: unknown, context: { agentId?: string |
|
|
|
423
424
|
try {
|
|
424
425
|
requestedChain = getWalletChainOrDefault(normalized.chain ?? normalized.provider, 'solana')
|
|
425
426
|
} catch (err: unknown) {
|
|
426
|
-
return JSON.stringify({ error:
|
|
427
|
+
return JSON.stringify({ error: errorMessage(err) })
|
|
427
428
|
}
|
|
428
429
|
|
|
429
430
|
const wallets = getWalletsByAgentId(agentId)
|
|
@@ -446,7 +447,7 @@ async function executeWalletAction(args: unknown, context: { agentId?: string |
|
|
|
446
447
|
],
|
|
447
448
|
})
|
|
448
449
|
} catch (err: unknown) {
|
|
449
|
-
return JSON.stringify({ error:
|
|
450
|
+
return JSON.stringify({ error: errorMessage(err) })
|
|
450
451
|
}
|
|
451
452
|
}
|
|
452
453
|
|
|
@@ -476,7 +477,7 @@ async function executeWalletAction(args: unknown, context: { agentId?: string |
|
|
|
476
477
|
],
|
|
477
478
|
})
|
|
478
479
|
} catch (err: unknown) {
|
|
479
|
-
return JSON.stringify({ error:
|
|
480
|
+
return JSON.stringify({ error: errorMessage(err) })
|
|
480
481
|
}
|
|
481
482
|
}
|
|
482
483
|
|
|
@@ -548,7 +549,7 @@ async function executeWalletAction(args: unknown, context: { agentId?: string |
|
|
|
548
549
|
if (BigInt(amountAtomic) <= BigInt(0)) return JSON.stringify({ error: 'amount must be positive' })
|
|
549
550
|
formattedAmount = formatWalletAmount(wallet.chain, amountAtomic, { minFractionDigits: 4, maxFractionDigits: 6 })
|
|
550
551
|
} catch (err: unknown) {
|
|
551
|
-
return JSON.stringify({ error:
|
|
552
|
+
return JSON.stringify({ error: errorMessage(err) })
|
|
552
553
|
}
|
|
553
554
|
|
|
554
555
|
const perTxLimitAtomic = getWalletLimitAtomic(wallet, 'perTx')
|
|
@@ -1167,7 +1168,7 @@ async function executeWalletAction(args: unknown, context: { agentId?: string |
|
|
|
1167
1168
|
return JSON.stringify({ error: `Unknown action: ${action}` })
|
|
1168
1169
|
}
|
|
1169
1170
|
} catch (err: unknown) {
|
|
1170
|
-
return JSON.stringify({ error:
|
|
1171
|
+
return JSON.stringify({ error: errorMessage(err) })
|
|
1171
1172
|
}
|
|
1172
1173
|
}
|
|
1173
1174
|
|
|
@@ -25,6 +25,7 @@ describe('browser tool connection config', () => {
|
|
|
25
25
|
|
|
26
26
|
it('strips host Playwright MCP env overrides before applying the local browser config', () => {
|
|
27
27
|
const env = sanitizePlaywrightMcpEnv({
|
|
28
|
+
NODE_ENV: 'test',
|
|
28
29
|
PLAYWRIGHT_MCP_CONFIG: '/tmp/evil-config.json',
|
|
29
30
|
PLAYWRIGHT_MCP_SHARED_BROWSER_CONTEXT: '1',
|
|
30
31
|
PLAYWRIGHT_MCP_TIMEOUT_ACTION: '999999',
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'url'
|
|
4
|
+
import { UPLOAD_DIR } from '../storage'
|
|
5
|
+
import { safePath, truncate, MAX_OUTPUT } from './context'
|
|
6
|
+
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
7
|
+
import type { SearchResult } from './search-providers'
|
|
8
|
+
|
|
9
|
+
function cleanSearchField(value: string | undefined): string {
|
|
10
|
+
return (value || '').replace(/\s+/g, ' ').trim()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function formatWebSearchResults(query: string, results: SearchResult[], maxChars = MAX_OUTPUT): string {
|
|
14
|
+
const cleanedQuery = cleanSearchField(query)
|
|
15
|
+
const header = cleanedQuery ? `Search results for: ${cleanedQuery}` : 'Search results'
|
|
16
|
+
const sections: string[] = [header]
|
|
17
|
+
const joinSections = (items: string[]) => items.filter(Boolean).join('\n\n')
|
|
18
|
+
|
|
19
|
+
for (let index = 0; index < results.length; index++) {
|
|
20
|
+
const result = results[index]
|
|
21
|
+
const title = cleanSearchField(result?.title) || cleanSearchField(result?.url) || `Result ${index + 1}`
|
|
22
|
+
const url = cleanSearchField(result?.url)
|
|
23
|
+
const snippet = cleanSearchField(result?.snippet)
|
|
24
|
+
const lines = [`${index + 1}. ${title}`]
|
|
25
|
+
if (url) lines.push(`URL: ${url}`)
|
|
26
|
+
if (snippet) lines.push(`Snippet: ${snippet}`)
|
|
27
|
+
const candidate = joinSections([...sections, lines.join('\n')])
|
|
28
|
+
if (candidate.length <= maxChars) {
|
|
29
|
+
sections.push(lines.join('\n'))
|
|
30
|
+
continue
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (url) {
|
|
34
|
+
const minimalLines = [`${index + 1}. ${title}`, `URL: ${url}`]
|
|
35
|
+
const minimalCandidate = joinSections([...sections, minimalLines.join('\n')])
|
|
36
|
+
if (minimalCandidate.length <= maxChars) {
|
|
37
|
+
sections.push(minimalLines.join('\n'))
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const omitted = results.length - index
|
|
42
|
+
if (omitted > 0) {
|
|
43
|
+
const remainingNotice = `(${omitted} additional result${omitted === 1 ? '' : 's'} omitted for brevity)`
|
|
44
|
+
const withNotice = joinSections([...sections, remainingNotice])
|
|
45
|
+
if (withNotice.length <= maxChars) sections.push(remainingNotice)
|
|
46
|
+
}
|
|
47
|
+
return truncate(joinSections(sections), maxChars)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return truncate(joinSections(sections), maxChars)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function buildBrowserConnectionOptions(profileDir: string) {
|
|
54
|
+
return {
|
|
55
|
+
browser: {
|
|
56
|
+
userDataDir: profileDir,
|
|
57
|
+
launchOptions: { headless: true },
|
|
58
|
+
contextOptions: {
|
|
59
|
+
viewport: { width: 1440, height: 900 },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
imageResponses: 'allow' as const,
|
|
63
|
+
capabilities: ['core', 'pdf', 'vision', 'network', 'storage'],
|
|
64
|
+
sharedBrowserContext: false,
|
|
65
|
+
timeouts: {
|
|
66
|
+
action: 15_000,
|
|
67
|
+
navigation: 60_000,
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function buildBrowserStdioServerParams(profileDir: string) {
|
|
73
|
+
const cliCandidates = [
|
|
74
|
+
path.join(process.cwd(), 'node_modules', '@playwright', 'mcp', 'cli.js'),
|
|
75
|
+
path.join(process.cwd(), '[project]', 'node_modules', '@playwright', 'mcp', 'cli.js'),
|
|
76
|
+
]
|
|
77
|
+
const cliPath = cliCandidates.find((candidate) => fs.existsSync(candidate)) || cliCandidates[0]
|
|
78
|
+
const outputDir = path.join(profileDir, 'mcp-output')
|
|
79
|
+
const env = sanitizePlaywrightMcpEnv()
|
|
80
|
+
return {
|
|
81
|
+
command: process.execPath,
|
|
82
|
+
args: [
|
|
83
|
+
cliPath,
|
|
84
|
+
'--headless',
|
|
85
|
+
'--user-data-dir', profileDir,
|
|
86
|
+
'--output-dir', outputDir,
|
|
87
|
+
'--caps', 'vision,pdf',
|
|
88
|
+
'--image-responses', 'allow',
|
|
89
|
+
'--output-mode', 'file',
|
|
90
|
+
'--timeout-action', '15000',
|
|
91
|
+
'--timeout-navigation', '60000',
|
|
92
|
+
],
|
|
93
|
+
env: {
|
|
94
|
+
...env,
|
|
95
|
+
PLAYWRIGHT_MCP_USER_DATA_DIR: profileDir,
|
|
96
|
+
PLAYWRIGHT_MCP_HEADLESS: '1',
|
|
97
|
+
PLAYWRIGHT_MCP_IMAGE_RESPONSES: 'allow',
|
|
98
|
+
PLAYWRIGHT_MCP_OUTPUT_DIR: outputDir,
|
|
99
|
+
PLAYWRIGHT_MCP_OUTPUT_MODE: 'file',
|
|
100
|
+
PLAYWRIGHT_MCP_TIMEOUT_ACTION: '15000',
|
|
101
|
+
PLAYWRIGHT_MCP_TIMEOUT_NAVIGATION: '60000',
|
|
102
|
+
},
|
|
103
|
+
stderr: 'inherit' as const,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function sanitizePlaywrightMcpEnv(baseEnv: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
|
|
108
|
+
const env: NodeJS.ProcessEnv = { ...baseEnv }
|
|
109
|
+
for (const key of Object.keys(env)) {
|
|
110
|
+
if (!key.toUpperCase().startsWith('PLAYWRIGHT_MCP_')) continue
|
|
111
|
+
delete env[key]
|
|
112
|
+
}
|
|
113
|
+
return env
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function inferWebActionFromArgs(params: {
|
|
117
|
+
action?: string
|
|
118
|
+
query?: string
|
|
119
|
+
url?: string
|
|
120
|
+
}): 'search' | 'fetch' | undefined {
|
|
121
|
+
if (params.action === 'search' || params.action === 'fetch') return params.action
|
|
122
|
+
if (typeof params.url === 'string' && /^https?:\/\//i.test(params.url.trim())) return 'fetch'
|
|
123
|
+
if (typeof params.query === 'string' && params.query.trim()) return 'search'
|
|
124
|
+
if (typeof params.url === 'string' && params.url.trim()) return 'search'
|
|
125
|
+
return undefined
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function parseStructuredJsonValue(value: unknown): unknown {
|
|
129
|
+
if (typeof value !== 'string') return value
|
|
130
|
+
const trimmed = value.trim()
|
|
131
|
+
if (!trimmed || (!trimmed.startsWith('{') && !trimmed.startsWith('['))) return value
|
|
132
|
+
try {
|
|
133
|
+
return JSON.parse(trimmed)
|
|
134
|
+
} catch {
|
|
135
|
+
return value
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function parseJsonObjectValue(value: unknown): Record<string, unknown> | null {
|
|
140
|
+
const parsed = parseStructuredJsonValue(value)
|
|
141
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
142
|
+
? parsed as Record<string, unknown>
|
|
143
|
+
: null
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function parseJsonArrayValue(value: unknown): unknown[] | null {
|
|
147
|
+
const parsed = parseStructuredJsonValue(value)
|
|
148
|
+
return Array.isArray(parsed) ? parsed : null
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function pickNonEmptyBrowserString(...values: unknown[]): string | undefined {
|
|
152
|
+
for (const value of values) {
|
|
153
|
+
if (typeof value !== 'string') continue
|
|
154
|
+
const trimmed = value.trim()
|
|
155
|
+
if (trimmed) return trimmed
|
|
156
|
+
}
|
|
157
|
+
return undefined
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function wrapBrowserEvaluateFunction(code: string): string {
|
|
161
|
+
const trimmed = code.trim()
|
|
162
|
+
if (!trimmed) return trimmed
|
|
163
|
+
if (/^(?:async\s+)?function\b/.test(trimmed)) return trimmed
|
|
164
|
+
if (/^(?:async\s*)?\([^)]*\)\s*=>/.test(trimmed)) return trimmed
|
|
165
|
+
return /[;{}]/.test(trimmed)
|
|
166
|
+
? `() => { ${trimmed} }`
|
|
167
|
+
: `() => (${trimmed})`
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function wrapBrowserRunCodeFunction(code: string): string {
|
|
171
|
+
const trimmed = code.trim()
|
|
172
|
+
if (!trimmed) return trimmed
|
|
173
|
+
if (/^(?:async\s+)?function\b/.test(trimmed)) return trimmed
|
|
174
|
+
if (/^(?:async\s*)?\([^)]*\)\s*=>/.test(trimmed)) return trimmed
|
|
175
|
+
return /[;{}]/.test(trimmed)
|
|
176
|
+
? `async (page) => { ${trimmed} }`
|
|
177
|
+
: `async (page) => (${trimmed})`
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function normalizeBrowserActionParams(rawParams: Record<string, unknown>): Record<string, unknown> {
|
|
181
|
+
const normalized = normalizeToolInputArgs(rawParams)
|
|
182
|
+
const action = String(normalized.action || '').trim().toLowerCase()
|
|
183
|
+
const params: Record<string, unknown> = { ...normalized }
|
|
184
|
+
|
|
185
|
+
const parsedFields = parseJsonArrayValue(params.fields)
|
|
186
|
+
if (parsedFields) params.fields = parsedFields
|
|
187
|
+
|
|
188
|
+
const parsedForm = parseJsonObjectValue(params.form)
|
|
189
|
+
if (parsedForm) params.form = parsedForm
|
|
190
|
+
|
|
191
|
+
if (typeof params.selector === 'string' && !pickNonEmptyBrowserString(params.element)) {
|
|
192
|
+
params.element = params.selector
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (action === 'submit_form' && typeof params.selector === 'string' && !pickNonEmptyBrowserString(params.submitElement)) {
|
|
196
|
+
params.submitElement = params.selector
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (action === 'select') {
|
|
200
|
+
const parsedValues = parseJsonArrayValue(params.values ?? params.option ?? params.value)
|
|
201
|
+
if (parsedValues) params.values = parsedValues
|
|
202
|
+
else if (params.values === undefined) {
|
|
203
|
+
const scalar = pickNonEmptyBrowserString(params.option, params.value, params.text)
|
|
204
|
+
if (scalar) params.values = [scalar]
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (action === 'evaluate' && !pickNonEmptyBrowserString(params.function)) {
|
|
209
|
+
const code = pickNonEmptyBrowserString(params.code, params.script, params.javascript, params.js)
|
|
210
|
+
if (code) params.function = wrapBrowserEvaluateFunction(code)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (action === 'run_code') {
|
|
214
|
+
const code = pickNonEmptyBrowserString(params.code, params.function, params.script, params.javascript, params.js)
|
|
215
|
+
if (code) params.code = wrapBrowserRunCodeFunction(code)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return params
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function pickBrowserTargetFromParams(params: Record<string, unknown>): string | null {
|
|
222
|
+
for (const value of [
|
|
223
|
+
params.url,
|
|
224
|
+
params.filePath,
|
|
225
|
+
params.path,
|
|
226
|
+
params.href,
|
|
227
|
+
params.link,
|
|
228
|
+
params.target,
|
|
229
|
+
params.page,
|
|
230
|
+
]) {
|
|
231
|
+
if (typeof value !== 'string') continue
|
|
232
|
+
const trimmed = value.trim()
|
|
233
|
+
if (trimmed) return trimmed
|
|
234
|
+
}
|
|
235
|
+
return null
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function resolveUploadFilePath(target: string): string | null {
|
|
239
|
+
const normalized = target.replace(/^sandbox:/, '')
|
|
240
|
+
const match = normalized.match(/^\/api\/uploads\/([^?#]+)/)
|
|
241
|
+
if (!match) return null
|
|
242
|
+
let decoded = match[1]
|
|
243
|
+
try {
|
|
244
|
+
decoded = decodeURIComponent(decoded)
|
|
245
|
+
} catch {
|
|
246
|
+
// keep raw segment
|
|
247
|
+
}
|
|
248
|
+
const safeName = decoded.replace(/[^a-zA-Z0-9._-]/g, '')
|
|
249
|
+
const resolved = path.join(UPLOAD_DIR, safeName)
|
|
250
|
+
return fs.existsSync(resolved) ? resolved : null
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function resolveBrowserFileUrlPath(target: string): string | null {
|
|
254
|
+
if (!/^file:/i.test(target)) return null
|
|
255
|
+
try {
|
|
256
|
+
const resolved = fileURLToPath(target)
|
|
257
|
+
return fs.existsSync(resolved) ? resolved : null
|
|
258
|
+
} catch {
|
|
259
|
+
return null
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function tryResolveBrowserLocalPath(cwd: string, target: string): string | null {
|
|
264
|
+
const uploadPath = resolveUploadFilePath(target)
|
|
265
|
+
if (uploadPath) return uploadPath
|
|
266
|
+
|
|
267
|
+
const fileUrlPath = resolveBrowserFileUrlPath(target)
|
|
268
|
+
if (fileUrlPath) return fileUrlPath
|
|
269
|
+
|
|
270
|
+
if (/^(?:https?:|about:|data:)/i.test(target)) return null
|
|
271
|
+
|
|
272
|
+
const normalized = target.replace(/^sandbox:/, '')
|
|
273
|
+
const looksLikePath = normalized.startsWith('/')
|
|
274
|
+
|| normalized.startsWith('./')
|
|
275
|
+
|| normalized.startsWith('../')
|
|
276
|
+
|| normalized.includes('/')
|
|
277
|
+
|| /\.(?:html?|xhtml|txt|md|json|ya?ml|csv|ts|tsx|js|jsx|mjs|cjs|css|png|jpe?g|gif|webp|svg|pdf)$/i.test(normalized)
|
|
278
|
+
if (!looksLikePath) return null
|
|
279
|
+
|
|
280
|
+
const candidates = new Set<string>()
|
|
281
|
+
if (path.isAbsolute(normalized)) candidates.add(normalized)
|
|
282
|
+
try { candidates.add(safePath(cwd, normalized)) } catch { /* ignore */ }
|
|
283
|
+
try { candidates.add(path.resolve(cwd, normalized)) } catch { /* ignore */ }
|
|
284
|
+
|
|
285
|
+
for (const candidate of candidates) {
|
|
286
|
+
if (!candidate || !fs.existsSync(candidate)) continue
|
|
287
|
+
const stat = fs.statSync(candidate)
|
|
288
|
+
if (stat.isDirectory()) {
|
|
289
|
+
const indexPath = path.join(candidate, 'index.html')
|
|
290
|
+
if (fs.existsSync(indexPath)) return indexPath
|
|
291
|
+
return null
|
|
292
|
+
}
|
|
293
|
+
return candidate
|
|
294
|
+
}
|
|
295
|
+
return null
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function localHtmlFileToDataUrl(filePath: string): string | null {
|
|
299
|
+
const ext = path.extname(filePath).toLowerCase()
|
|
300
|
+
if (ext !== '.html' && ext !== '.htm') return null
|
|
301
|
+
try {
|
|
302
|
+
const html = fs.readFileSync(filePath, 'utf8')
|
|
303
|
+
const hasRelativeAssetReferences = /<(?:script|img|source|video|audio)\b[^>]+\b(?:src|poster)\s*=\s*["'](?![a-z]+:|\/\/|#|\/)([^"']+)["']|<link\b[^>]+\bhref\s*=\s*["'](?![a-z]+:|\/\/|#|\/)([^"']+)["']/i.test(html)
|
|
304
|
+
if (hasRelativeAssetReferences) return null
|
|
305
|
+
return `data:text/html;charset=utf-8,${encodeURIComponent(html)}`
|
|
306
|
+
} catch {
|
|
307
|
+
return null
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function resolveBrowserNavigationTarget(cwd: string, target: string): string {
|
|
312
|
+
const trimmed = target.trim()
|
|
313
|
+
if (!trimmed) return trimmed
|
|
314
|
+
const localPath = tryResolveBrowserLocalPath(cwd, trimmed)
|
|
315
|
+
if (localPath) return localHtmlFileToDataUrl(localPath) || pathToFileURL(localPath).toString()
|
|
316
|
+
return trimmed.replace(/^sandbox:/, '')
|
|
317
|
+
}
|