@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.
Files changed (394) hide show
  1. package/README.md +9 -9
  2. package/bin/swarmclaw.js +5 -1
  3. package/bin/worker-cmd.js +73 -0
  4. package/package.json +2 -1
  5. package/src/app/api/agents/[id]/route.ts +17 -7
  6. package/src/app/api/agents/route.ts +21 -8
  7. package/src/app/api/approvals/route.test.ts +6 -6
  8. package/src/app/api/approvals/route.ts +2 -1
  9. package/src/app/api/auth/route.ts +2 -3
  10. package/src/app/api/chatrooms/[id]/chat/route.test.ts +299 -0
  11. package/src/app/api/chatrooms/[id]/chat/route.ts +3 -2
  12. package/src/app/api/chatrooms/[id]/route.ts +7 -6
  13. package/src/app/api/chats/[id]/chat/route.test.ts +496 -0
  14. package/src/app/api/chats/[id]/chat/route.ts +7 -3
  15. package/src/app/api/chats/[id]/clear/route.ts +9 -9
  16. package/src/app/api/chats/[id]/devserver/route.ts +2 -1
  17. package/src/app/api/chats/[id]/edit-resend/route.ts +3 -4
  18. package/src/app/api/chats/[id]/fork/route.ts +3 -5
  19. package/src/app/api/chats/[id]/restore/route.ts +6 -7
  20. package/src/app/api/chats/[id]/retry/route.ts +3 -4
  21. package/src/app/api/chats/[id]/route.ts +61 -62
  22. package/src/app/api/chats/route.ts +7 -1
  23. package/src/app/api/connectors/[id]/route.ts +7 -8
  24. package/src/app/api/connectors/route.ts +5 -4
  25. package/src/app/api/eval/run/route.ts +2 -1
  26. package/src/app/api/eval/suite/route.ts +2 -1
  27. package/src/app/api/external-agents/route.test.ts +1 -1
  28. package/src/app/api/external-agents/route.ts +2 -2
  29. package/src/app/api/files/serve/route.ts +1 -1
  30. package/src/app/api/gateways/[id]/route.ts +7 -5
  31. package/src/app/api/gateways/route.ts +1 -1
  32. package/src/app/api/knowledge/upload/route.ts +1 -1
  33. package/src/app/api/logs/route.ts +5 -7
  34. package/src/app/api/memory-images/[filename]/route.ts +2 -3
  35. package/src/app/api/openclaw/agent-files/route.ts +4 -3
  36. package/src/app/api/openclaw/approvals/route.ts +3 -4
  37. package/src/app/api/openclaw/config-sync/route.ts +3 -2
  38. package/src/app/api/openclaw/cron/route.ts +3 -2
  39. package/src/app/api/openclaw/dotenv-keys/route.ts +2 -1
  40. package/src/app/api/openclaw/exec-config/route.ts +3 -2
  41. package/src/app/api/openclaw/gateway/route.ts +5 -4
  42. package/src/app/api/openclaw/history/route.ts +3 -2
  43. package/src/app/api/openclaw/media/route.ts +2 -1
  44. package/src/app/api/openclaw/permissions/route.ts +3 -2
  45. package/src/app/api/openclaw/sandbox-env/route.ts +3 -2
  46. package/src/app/api/openclaw/skills/install/route.ts +2 -1
  47. package/src/app/api/openclaw/skills/remove/route.ts +2 -1
  48. package/src/app/api/openclaw/skills/route.ts +3 -2
  49. package/src/app/api/orchestrator/run/route.ts +5 -14
  50. package/src/app/api/perf/route.ts +43 -0
  51. package/src/app/api/plugins/dependencies/route.ts +2 -1
  52. package/src/app/api/plugins/install/route.ts +2 -1
  53. package/src/app/api/plugins/marketplace/route.ts +3 -2
  54. package/src/app/api/plugins/settings/route.ts +2 -1
  55. package/src/app/api/preview-server/route.ts +11 -10
  56. package/src/app/api/projects/[id]/route.ts +1 -1
  57. package/src/app/api/schedules/[id]/route.test.ts +128 -0
  58. package/src/app/api/schedules/[id]/route.ts +43 -43
  59. package/src/app/api/schedules/[id]/run/route.ts +11 -62
  60. package/src/app/api/schedules/route.ts +21 -87
  61. package/src/app/api/settings/route.ts +2 -0
  62. package/src/app/api/setup/doctor/route.ts +9 -8
  63. package/src/app/api/tasks/[id]/approve/route.ts +33 -30
  64. package/src/app/api/tasks/[id]/route.ts +12 -35
  65. package/src/app/api/tasks/import/github/route.ts +2 -1
  66. package/src/app/api/tasks/route.ts +79 -91
  67. package/src/app/api/wallets/[id]/approve/route.ts +2 -1
  68. package/src/app/api/wallets/[id]/route.ts +13 -19
  69. package/src/app/api/wallets/[id]/send/route.ts +2 -1
  70. package/src/app/api/wallets/route.ts +2 -1
  71. package/src/app/api/webhooks/[id]/route.ts +2 -1
  72. package/src/app/api/webhooks/route.test.ts +3 -1
  73. package/src/app/page.tsx +23 -331
  74. package/src/cli/index.js +19 -0
  75. package/src/cli/index.ts +38 -7
  76. package/src/cli/spec.js +9 -0
  77. package/src/components/activity/activity-feed.tsx +7 -4
  78. package/src/components/agents/agent-card.tsx +32 -6
  79. package/src/components/agents/agent-chat-list.tsx +55 -22
  80. package/src/components/agents/agent-files-editor.tsx +3 -2
  81. package/src/components/agents/agent-sheet.tsx +123 -22
  82. package/src/components/agents/inspector-panel.tsx +1 -1
  83. package/src/components/agents/openclaw-skills-panel.tsx +2 -1
  84. package/src/components/agents/trash-list.tsx +1 -1
  85. package/src/components/auth/access-key-gate.tsx +8 -2
  86. package/src/components/auth/setup-wizard.tsx +10 -9
  87. package/src/components/auth/user-picker.tsx +3 -2
  88. package/src/components/chat/chat-area.tsx +20 -1
  89. package/src/components/chat/chat-card.tsx +18 -3
  90. package/src/components/chat/chat-header.tsx +24 -4
  91. package/src/components/chat/chat-list.tsx +2 -11
  92. package/src/components/chat/heartbeat-history-panel.tsx +2 -1
  93. package/src/components/chat/message-bubble.tsx +45 -6
  94. package/src/components/chat/message-list.tsx +280 -145
  95. package/src/components/chat/streaming-bubble.tsx +217 -60
  96. package/src/components/chat/swarm-panel.test.ts +274 -0
  97. package/src/components/chat/swarm-panel.tsx +410 -0
  98. package/src/components/chat/swarm-status-card.tsx +346 -0
  99. package/src/components/chat/tool-call-bubble.tsx +48 -23
  100. package/src/components/chatrooms/chatroom-list.tsx +8 -5
  101. package/src/components/chatrooms/chatroom-message.tsx +10 -7
  102. package/src/components/chatrooms/chatroom-view.tsx +12 -9
  103. package/src/components/connectors/connector-health.tsx +6 -4
  104. package/src/components/connectors/connector-list.tsx +16 -11
  105. package/src/components/connectors/connector-sheet.tsx +12 -6
  106. package/src/components/home/home-view.tsx +38 -24
  107. package/src/components/input/chat-input.tsx +10 -1
  108. package/src/components/layout/app-layout.tsx +2 -38
  109. package/src/components/layout/sheet-layer.tsx +50 -0
  110. package/src/components/mcp-servers/mcp-server-list.tsx +37 -5
  111. package/src/components/mcp-servers/mcp-server-sheet.tsx +12 -2
  112. package/src/components/plugins/plugin-list.tsx +8 -4
  113. package/src/components/plugins/plugin-sheet.tsx +2 -1
  114. package/src/components/providers/provider-list.tsx +3 -2
  115. package/src/components/providers/provider-sheet.tsx +2 -1
  116. package/src/components/runs/run-list.tsx +11 -7
  117. package/src/components/schedules/schedule-card.tsx +5 -3
  118. package/src/components/shared/agent-switch-dialog.tsx +1 -1
  119. package/src/components/shared/attachment-chip.tsx +19 -3
  120. package/src/components/shared/notification-center.tsx +6 -3
  121. package/src/components/shared/settings/plugin-manager.tsx +3 -2
  122. package/src/components/shared/settings/section-embedding.tsx +2 -1
  123. package/src/components/shared/settings/section-orchestrator.tsx +2 -1
  124. package/src/components/shared/settings/section-user-preferences.tsx +107 -0
  125. package/src/components/shared/settings/settings-page.tsx +13 -9
  126. package/src/components/skills/clawhub-browser.tsx +15 -4
  127. package/src/components/skills/skill-list.tsx +15 -4
  128. package/src/components/tasks/approvals-panel.tsx +2 -1
  129. package/src/components/tasks/task-board.tsx +35 -37
  130. package/src/components/tasks/task-sheet.tsx +4 -3
  131. package/src/components/ui/full-screen-loader.tsx +164 -0
  132. package/src/components/wallets/wallet-approval-dialog.tsx +2 -1
  133. package/src/components/wallets/wallet-panel.tsx +6 -5
  134. package/src/components/wallets/wallet-section.tsx +3 -2
  135. package/src/components/webhooks/webhook-list.tsx +4 -5
  136. package/src/components/webhooks/webhook-sheet.tsx +6 -6
  137. package/src/hooks/use-app-bootstrap.ts +202 -0
  138. package/src/hooks/use-mounted-ref.ts +14 -0
  139. package/src/hooks/use-now.ts +31 -0
  140. package/src/hooks/use-openclaw-gateway.ts +2 -1
  141. package/src/instrumentation.ts +20 -8
  142. package/src/lib/agent-default-tools.test.ts +52 -0
  143. package/src/lib/agent-default-tools.ts +40 -0
  144. package/src/lib/api-client.test.ts +21 -0
  145. package/src/lib/api-client.ts +6 -11
  146. package/src/lib/canvas-content.test.ts +360 -0
  147. package/src/lib/chat-streaming-state.test.ts +49 -2
  148. package/src/lib/chat-streaming-state.ts +26 -10
  149. package/src/lib/fetch-timeout.test.ts +54 -0
  150. package/src/lib/fetch-timeout.ts +60 -3
  151. package/src/lib/live-tool-events.test.ts +77 -0
  152. package/src/lib/live-tool-events.ts +73 -0
  153. package/src/lib/local-observability.test.ts +2 -2
  154. package/src/lib/openclaw-endpoint.test.ts +1 -1
  155. package/src/lib/providers/anthropic.ts +12 -16
  156. package/src/lib/providers/index.ts +4 -2
  157. package/src/lib/providers/ollama.ts +9 -6
  158. package/src/lib/providers/openai.ts +11 -14
  159. package/src/lib/runtime-env.test.ts +8 -8
  160. package/src/lib/schedule-dedupe-advanced.test.ts +2 -2
  161. package/src/lib/schedule-dedupe.test.ts +1 -1
  162. package/src/lib/schedule-dedupe.ts +3 -2
  163. package/src/lib/server/agent-thread-session.test.ts +6 -6
  164. package/src/lib/server/agent-thread-session.ts +6 -9
  165. package/src/lib/server/alert-dispatch.ts +2 -1
  166. package/src/lib/server/api-routes.test.ts +6 -6
  167. package/src/lib/server/approval-connector-notify.test.ts +4 -4
  168. package/src/lib/server/approvals-auto-approve.test.ts +29 -29
  169. package/src/lib/server/approvals.test.ts +317 -0
  170. package/src/lib/server/approvals.ts +5 -4
  171. package/src/lib/server/autonomy-runtime.test.ts +11 -11
  172. package/src/lib/server/browser-state.ts +2 -2
  173. package/src/lib/server/capability-router.test.ts +1 -1
  174. package/src/lib/server/capability-router.ts +3 -2
  175. package/src/lib/server/chat-execution-advanced.test.ts +15 -2
  176. package/src/lib/server/chat-execution-connector-delivery.ts +67 -0
  177. package/src/lib/server/chat-execution-disabled.test.ts +3 -3
  178. package/src/lib/server/chat-execution-eval-history.test.ts +3 -3
  179. package/src/lib/server/chat-execution-heartbeat.test.ts +42 -1
  180. package/src/lib/server/chat-execution-session-sync.test.ts +119 -0
  181. package/src/lib/server/chat-execution-tool-events.ts +116 -0
  182. package/src/lib/server/chat-execution-utils.test.ts +479 -0
  183. package/src/lib/server/chat-execution-utils.ts +533 -0
  184. package/src/lib/server/chat-execution.ts +153 -748
  185. package/src/lib/server/chat-streaming-utils.ts +174 -0
  186. package/src/lib/server/chat-turn-tool-routing.ts +310 -0
  187. package/src/lib/server/chatroom-session-persistence.test.ts +2 -2
  188. package/src/lib/server/clawhub-client.ts +2 -1
  189. package/src/lib/server/collection-helpers.test.ts +92 -0
  190. package/src/lib/server/collection-helpers.ts +25 -3
  191. package/src/lib/server/connectors/access.ts +146 -0
  192. package/src/lib/server/connectors/bluebubbles.test.ts +1 -1
  193. package/src/lib/server/connectors/bluebubbles.ts +4 -4
  194. package/src/lib/server/connectors/commands.ts +367 -0
  195. package/src/lib/server/connectors/connector-routing.test.ts +4 -4
  196. package/src/lib/server/connectors/delivery.ts +142 -0
  197. package/src/lib/server/connectors/discord.ts +37 -40
  198. package/src/lib/server/connectors/email.ts +11 -10
  199. package/src/lib/server/connectors/googlechat.ts +4 -4
  200. package/src/lib/server/connectors/inbound-audio-transcription.ts +2 -1
  201. package/src/lib/server/connectors/ingress-delivery.ts +23 -0
  202. package/src/lib/server/connectors/manager-roundtrip.test.ts +300 -0
  203. package/src/lib/server/connectors/manager.test.ts +352 -77
  204. package/src/lib/server/connectors/manager.ts +134 -673
  205. package/src/lib/server/connectors/matrix.ts +4 -4
  206. package/src/lib/server/connectors/message-sentinel.ts +7 -0
  207. package/src/lib/server/connectors/openclaw.test.ts +1 -1
  208. package/src/lib/server/connectors/openclaw.ts +8 -10
  209. package/src/lib/server/connectors/outbox.test.ts +192 -0
  210. package/src/lib/server/connectors/outbox.ts +369 -0
  211. package/src/lib/server/connectors/pairing.test.ts +18 -1
  212. package/src/lib/server/connectors/pairing.ts +49 -4
  213. package/src/lib/server/connectors/policy.ts +9 -3
  214. package/src/lib/server/connectors/reconnect-state.ts +71 -0
  215. package/src/lib/server/connectors/response-media.ts +256 -0
  216. package/src/lib/server/connectors/runtime-state.ts +67 -0
  217. package/src/lib/server/connectors/session.test.ts +357 -0
  218. package/src/lib/server/connectors/session.ts +422 -0
  219. package/src/lib/server/connectors/signal.ts +7 -7
  220. package/src/lib/server/connectors/slack.ts +43 -43
  221. package/src/lib/server/connectors/teams.ts +4 -4
  222. package/src/lib/server/connectors/telegram.ts +37 -43
  223. package/src/lib/server/connectors/types.ts +31 -1
  224. package/src/lib/server/connectors/whatsapp.test.ts +108 -0
  225. package/src/lib/server/connectors/whatsapp.ts +106 -34
  226. package/src/lib/server/context-manager.test.ts +409 -0
  227. package/src/lib/server/cost.test.ts +1 -1
  228. package/src/lib/server/daemon-policy.ts +78 -0
  229. package/src/lib/server/daemon-state-connectors.test.ts +167 -0
  230. package/src/lib/server/daemon-state.test.ts +283 -55
  231. package/src/lib/server/daemon-state.ts +106 -109
  232. package/src/lib/server/data-dir.test.ts +5 -5
  233. package/src/lib/server/data-dir.ts +4 -0
  234. package/src/lib/server/delegation-jobs-advanced.test.ts +1 -1
  235. package/src/lib/server/delegation-jobs.test.ts +87 -0
  236. package/src/lib/server/delegation-jobs.ts +42 -48
  237. package/src/lib/server/devserver-launch.ts +1 -1
  238. package/src/lib/server/document-utils.ts +7 -9
  239. package/src/lib/server/elevenlabs.ts +2 -1
  240. package/src/lib/server/embeddings.test.ts +105 -0
  241. package/src/lib/server/ethereum.ts +3 -2
  242. package/src/lib/server/eval/agent-regression.ts +3 -2
  243. package/src/lib/server/eval/runner.ts +2 -1
  244. package/src/lib/server/eval/scorer.ts +2 -1
  245. package/src/lib/server/evm-swap.ts +2 -1
  246. package/src/lib/server/gateway/protocol.test.ts +1 -1
  247. package/src/lib/server/guardian.ts +2 -1
  248. package/src/lib/server/heartbeat-blocked-suppression.test.ts +151 -0
  249. package/src/lib/server/heartbeat-service-timer.test.ts +6 -6
  250. package/src/lib/server/heartbeat-service.test.ts +406 -0
  251. package/src/lib/server/heartbeat-service.ts +54 -7
  252. package/src/lib/server/heartbeat-wake.test.ts +19 -0
  253. package/src/lib/server/heartbeat-wake.ts +17 -16
  254. package/src/lib/server/integrity-monitor.test.ts +149 -0
  255. package/src/lib/server/json-utils.ts +22 -0
  256. package/src/lib/server/knowledge-db.test.ts +13 -13
  257. package/src/lib/server/link-understanding.ts +2 -1
  258. package/src/lib/server/llm-response-cache.test.ts +1 -1
  259. package/src/lib/server/main-agent-loop-advanced.test.ts +65 -3
  260. package/src/lib/server/main-agent-loop.test.ts +6 -6
  261. package/src/lib/server/main-agent-loop.ts +21 -7
  262. package/src/lib/server/mcp-client.test.ts +1 -1
  263. package/src/lib/server/mcp-conformance.test.ts +1 -1
  264. package/src/lib/server/mcp-conformance.ts +3 -2
  265. package/src/lib/server/memory-consolidation.ts +2 -1
  266. package/src/lib/server/memory-db.test.ts +485 -0
  267. package/src/lib/server/memory-db.ts +39 -26
  268. package/src/lib/server/memory-graph.test.ts +2 -2
  269. package/src/lib/server/memory-policy.test.ts +7 -7
  270. package/src/lib/server/memory-retrieval.test.ts +1 -1
  271. package/src/lib/server/openclaw-config-sync.ts +2 -1
  272. package/src/lib/server/openclaw-deploy.test.ts +1 -1
  273. package/src/lib/server/openclaw-deploy.ts +8 -12
  274. package/src/lib/server/openclaw-exec-config.ts +2 -1
  275. package/src/lib/server/openclaw-gateway.ts +6 -7
  276. package/src/lib/server/openclaw-skills-normalize.ts +2 -1
  277. package/src/lib/server/openclaw-sync.ts +7 -5
  278. package/src/lib/server/orchestrator-lg-structure.test.ts +17 -0
  279. package/src/lib/server/orchestrator-lg.ts +199 -327
  280. package/src/lib/server/path-utils.ts +31 -0
  281. package/src/lib/server/perf.ts +161 -0
  282. package/src/lib/server/plugins-approval-guidance.ts +115 -0
  283. package/src/lib/server/plugins.test.ts +1 -1
  284. package/src/lib/server/plugins.ts +22 -132
  285. package/src/lib/server/process-manager.ts +5 -8
  286. package/src/lib/server/provider-health.test.ts +137 -0
  287. package/src/lib/server/provider-health.ts +3 -3
  288. package/src/lib/server/provider-model-discovery.ts +3 -12
  289. package/src/lib/server/queue-followups.test.ts +9 -9
  290. package/src/lib/server/queue-reconcile.test.ts +2 -2
  291. package/src/lib/server/queue-recovery.test.ts +269 -0
  292. package/src/lib/server/queue.test.ts +570 -0
  293. package/src/lib/server/queue.ts +62 -455
  294. package/src/lib/server/resolve-image.ts +30 -0
  295. package/src/lib/server/runtime-settings.test.ts +4 -4
  296. package/src/lib/server/runtime-storage-write-paths.test.ts +60 -0
  297. package/src/lib/server/schedule-normalization.test.ts +279 -0
  298. package/src/lib/server/schedule-service.ts +263 -0
  299. package/src/lib/server/scheduler.ts +17 -74
  300. package/src/lib/server/session-mailbox.test.ts +191 -0
  301. package/src/lib/server/session-run-manager.test.ts +640 -0
  302. package/src/lib/server/session-run-manager.ts +59 -15
  303. package/src/lib/server/session-tools/autonomy-tools.test.ts +20 -20
  304. package/src/lib/server/session-tools/calendar.ts +2 -1
  305. package/src/lib/server/session-tools/canvas.ts +2 -1
  306. package/src/lib/server/session-tools/chatroom.ts +2 -1
  307. package/src/lib/server/session-tools/connector.ts +26 -28
  308. package/src/lib/server/session-tools/context-mgmt.ts +3 -2
  309. package/src/lib/server/session-tools/crawl.ts +4 -3
  310. package/src/lib/server/session-tools/crud.ts +105 -324
  311. package/src/lib/server/session-tools/delegate-fallback.test.ts +9 -9
  312. package/src/lib/server/session-tools/delegate.ts +6 -8
  313. package/src/lib/server/session-tools/discovery-approvals.test.ts +15 -15
  314. package/src/lib/server/session-tools/discovery.ts +4 -3
  315. package/src/lib/server/session-tools/document.ts +2 -1
  316. package/src/lib/server/session-tools/email.ts +2 -1
  317. package/src/lib/server/session-tools/extract.ts +2 -1
  318. package/src/lib/server/session-tools/file.ts +4 -3
  319. package/src/lib/server/session-tools/http.ts +2 -1
  320. package/src/lib/server/session-tools/human-loop.ts +2 -1
  321. package/src/lib/server/session-tools/image-gen.ts +4 -3
  322. package/src/lib/server/session-tools/index.ts +26 -30
  323. package/src/lib/server/session-tools/mailbox.ts +2 -1
  324. package/src/lib/server/session-tools/manage-connectors.test.ts +4 -4
  325. package/src/lib/server/session-tools/manage-schedules.test.ts +12 -12
  326. package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +5 -5
  327. package/src/lib/server/session-tools/manage-tasks.test.ts +2 -2
  328. package/src/lib/server/session-tools/monitor.ts +2 -1
  329. package/src/lib/server/session-tools/platform.ts +2 -1
  330. package/src/lib/server/session-tools/plugin-creator.ts +2 -1
  331. package/src/lib/server/session-tools/replicate.ts +3 -2
  332. package/src/lib/server/session-tools/session-tools-wiring.test.ts +6 -6
  333. package/src/lib/server/session-tools/shell.ts +4 -9
  334. package/src/lib/server/session-tools/subagent.ts +322 -170
  335. package/src/lib/server/session-tools/table.ts +6 -5
  336. package/src/lib/server/session-tools/wallet-tool.test.ts +3 -3
  337. package/src/lib/server/session-tools/wallet.ts +7 -6
  338. package/src/lib/server/session-tools/web-browser-config.test.ts +1 -0
  339. package/src/lib/server/session-tools/web-utils.ts +317 -0
  340. package/src/lib/server/session-tools/web.ts +62 -328
  341. package/src/lib/server/skill-prompt-budget.test.ts +1 -1
  342. package/src/lib/server/skills-normalize.ts +2 -1
  343. package/src/lib/server/storage-item-access.test.ts +302 -0
  344. package/src/lib/server/storage.ts +366 -314
  345. package/src/lib/server/stream-agent-chat.test.ts +82 -3
  346. package/src/lib/server/stream-agent-chat.ts +146 -510
  347. package/src/lib/server/stream-continuation.ts +412 -0
  348. package/src/lib/server/subagent-lineage.test.ts +647 -0
  349. package/src/lib/server/subagent-lineage.ts +435 -0
  350. package/src/lib/server/subagent-runtime.test.ts +484 -0
  351. package/src/lib/server/subagent-runtime.ts +419 -0
  352. package/src/lib/server/subagent-swarm.test.ts +391 -0
  353. package/src/lib/server/subagent-swarm.ts +564 -0
  354. package/src/lib/server/system-events.ts +3 -3
  355. package/src/lib/server/task-followups.test.ts +491 -0
  356. package/src/lib/server/task-followups.ts +391 -0
  357. package/src/lib/server/task-lifecycle.test.ts +205 -0
  358. package/src/lib/server/task-lifecycle.ts +200 -0
  359. package/src/lib/server/task-quality-gate.test.ts +1 -1
  360. package/src/lib/server/task-resume.ts +208 -0
  361. package/src/lib/server/task-service.test.ts +108 -0
  362. package/src/lib/server/task-service.ts +264 -0
  363. package/src/lib/server/task-validation.test.ts +1 -1
  364. package/src/lib/server/test-utils/run-with-temp-data-dir.ts +42 -0
  365. package/src/lib/server/tool-capability-policy.test.ts +2 -2
  366. package/src/lib/server/tool-capability-policy.ts +3 -2
  367. package/src/lib/server/tool-planning.ts +2 -1
  368. package/src/lib/server/tool-retry.ts +2 -3
  369. package/src/lib/server/wake-dispatcher.test.ts +303 -0
  370. package/src/lib/server/wake-dispatcher.ts +318 -0
  371. package/src/lib/server/wake-mode.test.ts +161 -0
  372. package/src/lib/server/wake-mode.ts +174 -0
  373. package/src/lib/server/wallet-service.ts +8 -9
  374. package/src/lib/server/watch-jobs.ts +2 -1
  375. package/src/lib/server/workspace-context.ts +2 -2
  376. package/src/lib/shared-utils.test.ts +142 -0
  377. package/src/lib/shared-utils.ts +62 -0
  378. package/src/lib/tool-event-summary.ts +2 -1
  379. package/src/lib/view-routes.test.ts +100 -0
  380. package/src/lib/wallet.test.ts +322 -6
  381. package/src/proxy.test.ts +4 -4
  382. package/src/proxy.ts +2 -3
  383. package/src/stores/set-if-changed.ts +40 -0
  384. package/src/stores/slices/agent-slice.ts +111 -0
  385. package/src/stores/slices/auth-slice.ts +25 -0
  386. package/src/stores/slices/data-slice.ts +301 -0
  387. package/src/stores/slices/index.ts +7 -0
  388. package/src/stores/slices/session-slice.ts +112 -0
  389. package/src/stores/slices/task-slice.ts +63 -0
  390. package/src/stores/slices/ui-slice.ts +192 -0
  391. package/src/stores/use-app-store.ts +17 -822
  392. package/src/stores/use-approval-store.ts +2 -1
  393. package/src/stores/use-chat-store.ts +8 -1
  394. 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] = Array.from(new Set(values.map((value) => scalarToString(value)).filter(Boolean)))
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 = Array.from(new Set([...by, ...metrics.map((metric) => metric.as || (metric.column ? `${metric.op}_${metric.column}` : metric.op))]))
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: Array.from(new Set([...left.headers, ...rightHeaders])),
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 = Array.from(new Set(table.rows.map((row) => scalarToString(row[columnField])).filter(Boolean)))
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: ${err instanceof Error ? err.message : String(err)}`
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: ${err instanceof Error ? err.message : String(err)}`)
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: err instanceof Error ? err.message : String(err) })
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: err instanceof Error ? err.message : String(err) })
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: err instanceof Error ? err.message : String(err) })
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: err instanceof Error ? err.message : String(err) })
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: err instanceof Error ? err.message : String(err) })
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
+ }