@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
@@ -50,7 +50,19 @@ export function ChatArea() {
50
50
  const removeSessionFromStore = useAppStore((s) => s.removeSession)
51
51
  const refreshSession = useAppStore((s) => s.refreshSession)
52
52
  const appSettings = useAppStore((s) => s.appSettings)
53
- const { messages, setMessages, streaming, streamingSessionId, sendMessage, stopStreaming, devServer: devServerStatus, setDevServer, debugOpen, setDebugOpen, ttsEnabled, previewContent, setPreviewContent } = useChatStore()
53
+ const messages = useChatStore((s) => s.messages)
54
+ const setMessages = useChatStore((s) => s.setMessages)
55
+ const streaming = useChatStore((s) => s.streaming)
56
+ const streamingSessionId = useChatStore((s) => s.streamingSessionId)
57
+ const sendMessage = useChatStore((s) => s.sendMessage)
58
+ const stopStreaming = useChatStore((s) => s.stopStreaming)
59
+ const devServerStatus = useChatStore((s) => s.devServer)
60
+ const setDevServer = useChatStore((s) => s.setDevServer)
61
+ const debugOpen = useChatStore((s) => s.debugOpen)
62
+ const setDebugOpen = useChatStore((s) => s.setDebugOpen)
63
+ const ttsEnabled = useChatStore((s) => s.ttsEnabled)
64
+ const previewContent = useChatStore((s) => s.previewContent)
65
+ const setPreviewContent = useChatStore((s) => s.setPreviewContent)
54
66
  const isDesktop = useMediaQuery('(min-width: 768px)')
55
67
 
56
68
  const agents = useAppStore((s) => s.agents)
@@ -218,6 +230,12 @@ export function ChatArea() {
218
230
 
219
231
  const refreshMessages = useCallback(async () => {
220
232
  if (!sessionId) return
233
+ // Skip message refresh while we're locally streaming this session —
234
+ // the SSE stream already handles the live display via StreamingBubble.
235
+ // Fetching messages here would replace the array with new objects on every
236
+ // WS notification, causing the full MessageList to re-render and flash.
237
+ const chatState = useChatStore.getState()
238
+ if (chatState.streaming && chatState.streamingSessionId === sessionId) return
221
239
  try {
222
240
  const msgs = await fetchMessages(sessionId)
223
241
  const previous = messagesRef.current
@@ -357,6 +375,7 @@ export function ChatArea() {
357
375
  return (
358
376
  <div className="flex-1 flex h-full min-h-0 min-w-0">
359
377
  <div
378
+ data-testid="chat-area"
360
379
  className="flex-1 flex flex-col h-full min-h-0 min-w-0 relative"
361
380
  onDragOver={handleDragOver}
362
381
  onDragEnter={handleDragEnter}
@@ -4,6 +4,7 @@ import { useState } from 'react'
4
4
  import { DEFAULT_HEARTBEAT_INTERVAL_SEC } from '@/lib/heartbeat-defaults'
5
5
  import type { Session } from '@/types'
6
6
  import { api } from '@/lib/api-client'
7
+ import { useNow } from '@/hooks/use-now'
7
8
  import { getSessionLastAssistantAt, getSessionLastMessage } from '@/lib/session-summary'
8
9
  import { useAppStore } from '@/stores/use-app-store'
9
10
  import { useChatStore } from '@/stores/use-chat-store'
@@ -12,9 +13,10 @@ import { ConnectorPlatformBadge, getSessionConnector } from '@/components/shared
12
13
  import { AgentAvatar } from '@/components/agents/agent-avatar'
13
14
  import { toast } from 'sonner'
14
15
 
15
- function timeAgo(ts: number): string {
16
+ function timeAgo(ts: number, now: number | null): string {
16
17
  if (!ts) return ''
17
- const s = Math.floor((Date.now() - ts) / 1000)
18
+ if (!now) return 'recently'
19
+ const s = Math.floor((now - ts) / 1000)
18
20
  if (s < 60) return 'now'
19
21
  if (s < 3600) return Math.floor(s / 60) + 'm'
20
22
  if (s < 86400) return Math.floor(s / 3600) + 'h'
@@ -39,6 +41,7 @@ interface Props {
39
41
  }
40
42
 
41
43
  export function ChatCard({ session, active, onClick }: Props) {
44
+ const now = useNow({ enabled: false })
42
45
  const removeSession = useAppStore((s) => s.removeSession)
43
46
  const appSettings = useAppStore((s) => s.appSettings)
44
47
  const agents = useAppStore((s) => s.agents)
@@ -96,6 +99,18 @@ export function ChatCard({ session, active, onClick }: Props) {
96
99
  <>
97
100
  <div
98
101
  onClick={onClick}
102
+ onKeyDown={(event) => {
103
+ if (event.key === 'Enter' || event.key === ' ') {
104
+ event.preventDefault()
105
+ onClick()
106
+ }
107
+ }}
108
+ role="button"
109
+ tabIndex={0}
110
+ aria-label={`Open chat ${displayName}`}
111
+ data-testid="chat-row"
112
+ data-session-id={session.id}
113
+ data-agent-id={session.agentId || undefined}
99
114
  className={`group/card relative py-3.5 px-4 cursor-pointer rounded-[14px]
100
115
  transition-all duration-200 active:scale-[0.98]
101
116
  ${active
@@ -139,7 +154,7 @@ export function ChatCard({ session, active, onClick }: Props) {
139
154
  ) : null
140
155
  })()}
141
156
  <span className="text-[11px] text-text-3/70 shrink-0 tabular-nums font-mono">
142
- {timeAgo(session.lastActiveAt)}
157
+ {timeAgo(session.lastActiveAt, now)}
143
158
  </span>
144
159
  <button
145
160
  onClick={handleDeleteClick}
@@ -3,8 +3,10 @@
3
3
  import { DEFAULT_HEARTBEAT_INTERVAL_SEC } from '@/lib/heartbeat-defaults'
4
4
  import { useEffect, useState, useMemo, useRef, useCallback, type ReactNode } from 'react'
5
5
  import type { Session } from '@/types'
6
+ import { dedup } from '@/lib/shared-utils'
6
7
  import { useAppStore } from '@/stores/use-app-store'
7
8
  import { useChatStore } from '@/stores/use-chat-store'
9
+ import { useNow } from '@/hooks/use-now'
8
10
  import { IconButton } from '@/components/shared/icon-button'
9
11
  import { ChatToolToggles } from './chat-tool-toggles'
10
12
  import { api } from '@/lib/api-client'
@@ -41,7 +43,7 @@ function getAgentWalletIds(agent: { walletIds?: string[]; walletId?: string | nu
41
43
  const legacy = typeof agent?.walletId === 'string' && agent.walletId.trim()
42
44
  ? [agent.walletId.trim()]
43
45
  : []
44
- return [...new Set([...ids, ...legacy])]
46
+ return dedup([...ids, ...legacy])
45
47
  }
46
48
 
47
49
  function getAgentActiveWalletId(agent: { activeWalletId?: string | null; walletIds?: string[]; walletId?: string | null } | null | undefined): string | null {
@@ -127,6 +129,7 @@ interface Props {
127
129
  }
128
130
 
129
131
  export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, mobile, browserActive, onStopBrowser, onVoiceToggle, voiceActive, voiceSupported, heartbeatHistoryOpen, onToggleHeartbeatHistory, connectorSources, connectorFilter, onConnectorFilterChange, hasMultipleSources }: Props) {
132
+ const now = useNow()
130
133
  const ttsEnabled = useChatStore((s) => s.ttsEnabled)
131
134
  const toggleTts = useChatStore((s) => s.toggleTts)
132
135
  const soundEnabled = useChatStore((s) => s.soundEnabled)
@@ -171,9 +174,14 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
171
174
  const setWalletPanelAgentId = useAppStore((s) => s.setWalletPanelAgentId)
172
175
  const [walletBalance, setWalletBalance] = useState<{ formatted: string; symbol: string; assets?: number } | null>(null)
173
176
  const [headerWidgets, setHeaderWidgets] = useState<Array<{ id: string; label: string; icon?: string }>>([])
177
+ const [localhostBrowser, setLocalhostBrowser] = useState(false)
174
178
  const agentWalletIds = useMemo(() => getAgentWalletIds(agent), [agent])
175
179
  const activeWalletId = useMemo(() => getAgentActiveWalletId(agent), [agent])
176
180
 
181
+ useEffect(() => {
182
+ setLocalhostBrowser(window.location.hostname === 'localhost')
183
+ }, [])
184
+
177
185
  const refreshHeaderWidgets = useCallback(() => {
178
186
  api<Array<{ id: string; label: string; icon?: string }>>('GET', '/plugins/ui?type=header').then((widgets) => {
179
187
  if (Array.isArray(widgets)) setHeaderWidgets(widgets)
@@ -231,7 +239,14 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
231
239
  textClass: 'text-text-3/45',
232
240
  }
233
241
  }
234
- const ago = Date.now() - lastAt
242
+ if (!now) {
243
+ return {
244
+ label: 'Idle',
245
+ dotClass: 'bg-text-3/30',
246
+ textClass: 'text-text-3/45',
247
+ }
248
+ }
249
+ const ago = now - lastAt
235
250
  if (ago < 5 * 60_000) {
236
251
  return {
237
252
  label: 'Active',
@@ -251,7 +266,7 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
251
266
  dotClass: 'bg-text-3/30',
252
267
  textClass: 'text-text-3/45',
253
268
  }
254
- }, [connector, connectorPresence?.lastMessageAt])
269
+ }, [connector, connectorPresence?.lastMessageAt, now])
255
270
 
256
271
  const visibleHeaderWidgets = useMemo(() => {
257
272
  const seen = new Set<string>()
@@ -263,6 +278,11 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
263
278
  })
264
279
  }, [headerWidgets])
265
280
 
281
+ const heartbeatIntervalOptions = useMemo(
282
+ () => [...(localhostBrowser ? [60, 300] : []), 1800, 3600, 7200, 21600, 43200],
283
+ [localhostBrowser],
284
+ )
285
+
266
286
  const walletHeaderMeta = useMemo(() => {
267
287
  if (!agent?.id) {
268
288
  return {
@@ -869,7 +889,7 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
869
889
  </Tip>
870
890
  {hbDropdownOpen && (
871
891
  <div className="absolute top-full right-0 mt-1 py-1 rounded-[10px] border border-white/[0.06] bg-bg/95 backdrop-blur-md shadow-lg z-50 min-w-[88px]">
872
- {[...(typeof window !== 'undefined' && window.location.hostname === 'localhost' ? [60, 300] : []), 1800, 3600, 7200, 21600, 43200].map((sec) => (
892
+ {heartbeatIntervalOptions.map((sec) => (
873
893
  <button
874
894
  key={sec}
875
895
  onClick={() => handleSelectHeartbeatInterval(sec)}
@@ -2,9 +2,7 @@
2
2
 
3
3
  import { useEffect, useMemo, useState } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
- import { useChatStore } from '@/stores/use-chat-store'
6
5
  import { ChatCard } from './chat-card'
7
- import { fetchMessages } from '@/lib/chats'
8
6
  import { getSessionLastAssistantAt, getSessionLastMessage, getSessionMessageCount } from '@/lib/session-summary'
9
7
  import { isLocalhostBrowser, isVisibleSessionForViewer } from '@/lib/local-observability'
10
8
  import { toast } from 'sonner'
@@ -34,7 +32,6 @@ export function ChatList({ inSidebar, onSelect }: Props) {
34
32
  const lastReadTimestamps = useAppStore((s) => s.lastReadTimestamps)
35
33
  const agents = useAppStore((s) => s.agents)
36
34
  const connectors = useAppStore((s) => s.connectors)
37
- const setMessages = useChatStore((s) => s.setMessages)
38
35
  const [search, setSearch] = useState('')
39
36
  const [typeFilter, setTypeFilter] = useState<SessionFilter>('all')
40
37
  const [sortMode, setSortMode] = useState<SortMode>('lastActive')
@@ -111,14 +108,6 @@ export function ChatList({ inSidebar, onSelect }: Props) {
111
108
  if (typeof window !== 'undefined') {
112
109
  window.dispatchEvent(new CustomEvent('swarmclaw:scroll-bottom'))
113
110
  }
114
- try {
115
- const msgs = await fetchMessages(id)
116
- setMessages(msgs)
117
- } catch {
118
- const fallback = sessions[id]
119
- const fallbackLastMessage = fallback ? getSessionLastMessage(fallback) : null
120
- setMessages(fallback?.messages?.length ? fallback.messages : (fallbackLastMessage ? [fallbackLastMessage] : []))
121
- }
122
111
  onSelect?.()
123
112
  }
124
113
 
@@ -214,6 +203,8 @@ export function ChatList({ inSidebar, onSelect }: Props) {
214
203
  value={search}
215
204
  onChange={(e) => setSearch(e.target.value)}
216
205
  placeholder="Search..."
206
+ aria-label="Search chats"
207
+ data-testid="chat-search"
217
208
  className="flex-1 px-4 py-2.5 rounded-[12px] border border-white/[0.04] bg-surface text-text
218
209
  text-[13px] outline-none transition-all duration-200 placeholder:text-text-3/70 focus-glow"
219
210
  style={{ fontFamily: 'inherit' }}
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useEffect, useRef, useState, useMemo } from 'react'
4
4
  import type { Message } from '@/types'
5
+ import { dedup } from '@/lib/shared-utils'
5
6
 
6
7
  /* ─── Heartbeat meta parsing (shared with message-bubble) ─── */
7
8
 
@@ -203,7 +204,7 @@ export function HeartbeatHistoryPanel({ messages, agentHeartbeatGoal, onClose }:
203
204
  const goal = entry.meta?.goal || agentHeartbeatGoal
204
205
  const isExpanded = expandedIdx === i
205
206
  const toolNames = entry.msg.toolEvents?.map((te) => te.name).filter(Boolean) ?? []
206
- const uniqueTools = [...new Set(toolNames)]
207
+ const uniqueTools = dedup(toolNames)
207
208
 
208
209
  return (
209
210
  <button
@@ -5,6 +5,7 @@ import ReactMarkdown from 'react-markdown'
5
5
  import remarkGfm from 'remark-gfm'
6
6
  import rehypeHighlight from 'rehype-highlight'
7
7
  import type { Message } from '@/types'
8
+ import { useMediaQuery } from '@/hooks/use-media-query'
8
9
  import { useAppStore } from '@/stores/use-app-store'
9
10
  import { useChatStore } from '@/stores/use-chat-store'
10
11
  import { AiAvatar } from '@/components/shared/avatar'
@@ -17,6 +18,8 @@ import { isStructuredMarkdown } from './markdown-utils'
17
18
  import { FilePathChip, FILE_PATH_RE, DIR_PATH_RE } from './file-path-chip'
18
19
  import { TransferAgentPicker } from './transfer-agent-picker'
19
20
  import { DelegationBanner, DelegationSourceBanner, TaskCompletionCard, parseTaskCompletion } from './delegation-banner'
21
+ import { SwarmPanel, parseSwarmOutput, parseSwarmStatusOutput } from './swarm-panel'
22
+ import { SwarmStatusCard, SwarmStatusStyles } from './swarm-status-card'
20
23
  import { ConnectorPlatformIcon, getConnectorPlatformLabel } from '@/components/shared/connector-platform-icon'
21
24
  import { copyTextToClipboard } from '@/lib/clipboard'
22
25
  import { formatMessageTimestamp } from '@/lib/chat-display'
@@ -93,7 +96,10 @@ const emptyToolEvents: NonNullable<Message['toolEvents']> = []
93
96
  // AttachmentChip, parseAttachmentUrl, regex constants, and FILE_TYPE_COLORS
94
97
  // are now imported from @/components/shared/attachment-chip
95
98
 
96
- function renderAttachments(message: Message) {
99
+ function renderAttachments(
100
+ message: Message,
101
+ onOpenImage?: (image: { url: string; filename: string }) => void,
102
+ ) {
97
103
  const isUser = message.role === 'user'
98
104
  const seen = new Set<string>()
99
105
  const chips: { url: string; filename: string }[] = []
@@ -121,7 +127,15 @@ function renderAttachments(message: Message) {
121
127
  if (!chips.length) return null
122
128
  return (
123
129
  <div className="flex flex-col">
124
- {chips.map((c) => <AttachmentChip key={c.url} url={c.url} filename={c.filename} isUserMsg={isUser} />)}
130
+ {chips.map((c) => (
131
+ <AttachmentChip
132
+ key={c.url}
133
+ url={c.url}
134
+ filename={c.filename}
135
+ isUserMsg={isUser}
136
+ onOpenImage={onOpenImage}
137
+ />
138
+ ))}
125
139
  </div>
126
140
  )
127
141
  }
@@ -182,6 +196,8 @@ export const MessageBubble = memo(function MessageBubble({ message, assistantNam
182
196
  return null
183
197
  }, [message.text, isUser])
184
198
  const currentUser = useAppStore((s) => s.currentUser)
199
+ const isDesktop = useMediaQuery('(min-width: 768px)')
200
+ const setPreviewContent = useChatStore((s) => s.setPreviewContent)
185
201
  const [copied, setCopied] = useState(false)
186
202
  const [heartbeatExpanded, setHeartbeatExpanded] = useState(false)
187
203
  const [toolEventsExpanded, setToolEventsExpanded] = useState(false)
@@ -254,6 +270,10 @@ export const MessageBubble = memo(function MessageBubble({ message, assistantNam
254
270
  ? rawDisplayText.split('\n').filter((l) => !/\[(MAIN_LOOP_META|MAIN_LOOP_PLAN|MAIN_LOOP_REVIEW|AGENT_HEARTBEAT_META)\]/.test(l)).join('\n').trim()
255
271
  : ''
256
272
 
273
+ const handleOpenAttachmentImage = useCallback(({ url, filename }: { url: string; filename: string }) => {
274
+ setPreviewContent({ type: 'image', url, title: filename })
275
+ }, [setPreviewContent])
276
+
257
277
  const handleCopy = useCallback(() => {
258
278
  void copyTextToClipboard(message.text).then((copiedText) => {
259
279
  if (!copiedText) return
@@ -266,6 +286,11 @@ export const MessageBubble = memo(function MessageBubble({ message, assistantNam
266
286
 
267
287
  return (
268
288
  <div
289
+ data-testid="message-bubble"
290
+ data-message-role={message.role}
291
+ data-message-kind={message.kind || 'chat'}
292
+ data-message-time={message.time || undefined}
293
+ data-message-has-tools={hasToolEvents || undefined}
269
294
  className={`group ${isUser ? 'flex flex-col items-end' : 'flex flex-col items-start relative pl-[44px]'}`}
270
295
  >
271
296
  {/* Avatar on spine (assistant) */}
@@ -305,7 +330,7 @@ export const MessageBubble = memo(function MessageBubble({ message, assistantNam
305
330
 
306
331
  {/* Tool call events (assistant messages only) */}
307
332
  {hasToolEvents && (
308
- <div className="max-w-[85%] md:max-w-[72%] flex flex-col gap-2 mb-2">
333
+ <div className="max-w-[85%] md:max-w-[72%] flex flex-col gap-2 mb-2" data-testid="message-tool-events">
309
334
  {nonSendFileEvents.length > 1 && (
310
335
  <button
311
336
  type="button"
@@ -317,7 +342,20 @@ export const MessageBubble = memo(function MessageBubble({ message, assistantNam
317
342
  )}
318
343
  <div className={`${toolEventsExpanded ? 'max-h-[320px] overflow-y-auto pr-1 flex flex-col gap-2' : 'flex flex-col gap-2'}`}>
319
344
  {visibleToolEvents.map((event, i) => {
320
- const key = `${message.time}-tool-${toolEventsExpanded ? `all-${i}` : `latest-${nonSendFileEvents.length - 1}`}`
345
+ const eventKey = event.toolCallId || `${message.time}-${event.name}-${event.input}`
346
+ const key = `${message.time}-tool-${toolEventsExpanded ? `all-${eventKey}` : `latest-${eventKey}`}`
347
+
348
+ if (event.name === 'spawn_subagent' && event.output) {
349
+ // Prefer rich SwarmStatusCard when snapshot data is available
350
+ const statusData = parseSwarmStatusOutput(event.name, event.output)
351
+ if (statusData) {
352
+ return <div key={key}><SwarmStatusStyles /><SwarmStatusCard data={statusData} /></div>
353
+ }
354
+ const swarmData = parseSwarmOutput(event.name, event.output)
355
+ if (swarmData) {
356
+ return <SwarmPanel key={key} data={swarmData} />
357
+ }
358
+ }
321
359
 
322
360
  if (event.name === 'delegate_to_agent') {
323
361
  const inp = tryParseJson(event.input || '{}')
@@ -356,11 +394,12 @@ export const MessageBubble = memo(function MessageBubble({ message, assistantNam
356
394
  <ToolCallBubble
357
395
  key={key}
358
396
  event={{
359
- id: `${message.time}-${toolEventsExpanded ? i : toolEvents.length - 1}`,
397
+ id: event.toolCallId || `${message.time}-${toolEventsExpanded ? i : toolEvents.length - 1}`,
360
398
  name: event.name,
361
399
  input: event.input,
362
400
  output: event.output,
363
401
  status: event.error ? 'error' : 'done',
402
+
364
403
  }}
365
404
  />
366
405
  )
@@ -417,7 +456,7 @@ export const MessageBubble = memo(function MessageBubble({ message, assistantNam
417
456
  ) : (
418
457
  /* Message bubble */
419
458
  <div className={`${isStructured ? 'max-w-[92%] md:max-w-[85%]' : 'max-w-[85%] md:max-w-[72%]'} ${isUser ? 'bubble-user px-5 py-3.5' : isHeartbeat ? 'bubble-ai px-4 py-3' : 'bubble-ai px-5 py-3.5'}`}>
420
- {renderAttachments(message)}
459
+ {renderAttachments(message, isDesktop ? handleOpenAttachmentImage : undefined)}
421
460
 
422
461
  {walletRequest ? (
423
462
  <div className="flex flex-col gap-3 p-4 rounded-[18px] bg-sky-500/[0.03] border border-sky-500/20 shadow-[0_0_20px_rgba(14,165,233,0.05)]">