@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
@@ -1,8 +1,10 @@
1
1
  import crypto from 'node:crypto'
2
2
  import fs from 'node:fs'
3
3
  import path from 'node:path'
4
+ import type { WhatsAppApprovedContact } from '@/types'
5
+ import { CONNECTORS_DATA_DIR } from '../data-dir'
6
+ import { safeJsonParseObject } from '../json-utils'
4
7
 
5
- const DEFAULT_DATA_DIR = path.join(process.cwd(), 'data')
6
8
  const STORE_VERSION = 1
7
9
  const PENDING_TTL_MS = 24 * 60 * 60 * 1000
8
10
  const MAX_PENDING_PER_CONNECTOR = 100
@@ -10,8 +12,7 @@ const PAIR_CODE_LENGTH = 8
10
12
  const PAIR_CODE_ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'
11
13
 
12
14
  function resolveStorePath(): string {
13
- const dataDir = process.env.DATA_DIR || DEFAULT_DATA_DIR
14
- return path.join(dataDir, 'connectors', 'pairing-store.json')
15
+ return path.join(CONNECTORS_DATA_DIR, 'pairing-store.json')
15
16
  }
16
17
 
17
18
  export type PairingPolicy = 'open' | 'allowlist' | 'pairing' | 'disabled'
@@ -68,6 +69,24 @@ function dedupe(items: string[]): string[] {
68
69
  return out
69
70
  }
70
71
 
72
+ function normalizeApprovedContactPhone(value: unknown): string {
73
+ return typeof value === 'string' ? value.trim() : ''
74
+ }
75
+
76
+ function normalizeApprovedContactLabel(value: unknown, fallback: string): string {
77
+ const trimmed = typeof value === 'string' ? value.trim() : ''
78
+ return trimmed || fallback
79
+ }
80
+
81
+ function approvedContactKey(phone: string): string {
82
+ const normalized = normalizeSenderId(phone)
83
+ if (!normalized) return ''
84
+ const digits = normalized.replace(/[^\d]/g, '')
85
+ if (digits) return digits
86
+ const jidUser = normalized.split('@')[0]?.split(':')[0]?.trim()
87
+ return jidUser || normalized
88
+ }
89
+
71
90
  function prunePending(entries: PairingRequest[]): PairingRequest[] {
72
91
  const now = Date.now()
73
92
  return entries.filter((entry) => {
@@ -86,7 +105,7 @@ function loadStore(): PairingStore {
86
105
  try {
87
106
  if (!fs.existsSync(storePath)) return emptyStore()
88
107
  const raw = fs.readFileSync(storePath, 'utf8')
89
- const parsed = JSON.parse(raw) as PairingStore
108
+ const parsed = safeJsonParseObject<PairingStore>(raw)
90
109
  if (!parsed || typeof parsed !== 'object' || typeof parsed.connectors !== 'object') {
91
110
  return emptyStore()
92
111
  }
@@ -151,6 +170,32 @@ export function parseAllowFromCsv(value: unknown): string[] {
151
170
  return dedupe(value.split(',').map((item) => item.trim()).filter(Boolean))
152
171
  }
153
172
 
173
+ export function normalizeWhatsAppApprovedContacts(value: unknown): WhatsAppApprovedContact[] {
174
+ if (!Array.isArray(value)) return []
175
+
176
+ const seen = new Set<string>()
177
+ const out: WhatsAppApprovedContact[] = []
178
+ for (const entry of value) {
179
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) continue
180
+ const record = entry as Record<string, unknown>
181
+ const phone = normalizeApprovedContactPhone(record.phone)
182
+ if (!phone) continue
183
+ const dedupeKey = approvedContactKey(phone)
184
+ if (!dedupeKey || seen.has(dedupeKey)) continue
185
+ seen.add(dedupeKey)
186
+ out.push({
187
+ id: typeof record.id === 'string' && record.id.trim() ? record.id.trim() : `wa-contact-${out.length + 1}`,
188
+ label: normalizeApprovedContactLabel(record.label, phone),
189
+ phone,
190
+ })
191
+ }
192
+ return out
193
+ }
194
+
195
+ export function getWhatsAppApprovedSenderIds(value: unknown): string[] {
196
+ return normalizeWhatsAppApprovedContacts(value).map((entry) => entry.phone)
197
+ }
198
+
154
199
  export function listStoredAllowedSenders(connectorId: string): string[] {
155
200
  const store = loadStore()
156
201
  const state = ensureConnectorState(store, connectorId)
@@ -2,8 +2,8 @@ import type { Connector, Session, SessionResetMode, SessionResetType } from '@/t
2
2
  import { getProvider } from '@/lib/providers'
3
3
  import type { InboundMessage } from './types'
4
4
  import { evaluateSessionFreshness, inferSessionResetType, resetSessionRuntime, resolveSessionResetPolicy } from '../session-reset-policy'
5
- import { listStoredAllowedSenders, parseAllowFromCsv, parsePairingPolicy } from './pairing'
6
- import { loadAgents, loadChatrooms, loadCredentials } from '../storage'
5
+ import { getWhatsAppApprovedSenderIds, listStoredAllowedSenders, parseAllowFromCsv, parsePairingPolicy } from './pairing'
6
+ import { loadAgents, loadChatrooms, loadCredentials, loadSettings } from '../storage'
7
7
 
8
8
  export type ConnectorSessionScope = 'main' | 'channel' | 'peer' | 'channel-peer' | 'thread'
9
9
  export type ConnectorReplyMode = 'off' | 'first' | 'all'
@@ -377,7 +377,13 @@ export function buildConnectorDoctorWarnings(params: {
377
377
  }
378
378
  }
379
379
  const dmPolicy = parsePairingPolicy(connector.config?.dmPolicy, 'open')
380
- const configuredAllowFrom = parseAllowFromCsv(connector.config?.allowFrom)
380
+ const globalWhatsAppAllowFrom = connector.platform === 'whatsapp'
381
+ ? getWhatsAppApprovedSenderIds(loadSettings().whatsappApprovedContacts)
382
+ : []
383
+ const configuredAllowFrom = parseAllowFromCsv([
384
+ connector.config?.allowFrom,
385
+ ...globalWhatsAppAllowFrom,
386
+ ].filter(Boolean).join(','))
381
387
  const storedAllowFrom = listStoredAllowedSenders(connector.id)
382
388
  if (parseBool(connector.config?.statusReactions, true) && connector.platform === 'telegram') {
383
389
  warnings.push('Status reactions are enabled, but Telegram support is partial and may no-op depending on bot permissions.')
@@ -0,0 +1,71 @@
1
+ import { connectorRuntimeState, type ConnectorReconnectState } from './runtime-state'
2
+
3
+ export type { ConnectorReconnectState }
4
+
5
+ interface ConnectorReconnectPolicy {
6
+ initialBackoffMs?: number
7
+ maxBackoffMs?: number
8
+ maxAttempts?: number
9
+ }
10
+
11
+ export const connectorReconnectStateStore: Map<string, ConnectorReconnectState> =
12
+ connectorRuntimeState.reconnectStates
13
+
14
+ const RECONNECT_INITIAL_BACKOFF_MS = 1_000
15
+ const RECONNECT_MAX_BACKOFF_MS = 5 * 60 * 1_000
16
+ const RECONNECT_MAX_ATTEMPTS = 10
17
+
18
+ export function createConnectorReconnectState(
19
+ init: Partial<ConnectorReconnectState> = {},
20
+ policy: ConnectorReconnectPolicy = {},
21
+ ): ConnectorReconnectState {
22
+ return {
23
+ attempts: init.attempts ?? 0,
24
+ lastAttemptAt: init.lastAttemptAt ?? 0,
25
+ nextRetryAt: init.nextRetryAt ?? 0,
26
+ backoffMs: init.backoffMs ?? policy.initialBackoffMs ?? RECONNECT_INITIAL_BACKOFF_MS,
27
+ error: init.error ?? '',
28
+ exhausted: init.exhausted ?? false,
29
+ }
30
+ }
31
+
32
+ export function advanceConnectorReconnectState(
33
+ previous: ConnectorReconnectState,
34
+ error: string,
35
+ now = Date.now(),
36
+ policy: ConnectorReconnectPolicy = {},
37
+ ): ConnectorReconnectState {
38
+ const initialBackoffMs = policy.initialBackoffMs ?? RECONNECT_INITIAL_BACKOFF_MS
39
+ const maxBackoffMs = policy.maxBackoffMs ?? RECONNECT_MAX_BACKOFF_MS
40
+ const maxAttempts = policy.maxAttempts ?? RECONNECT_MAX_ATTEMPTS
41
+ const attempts = previous.attempts + 1
42
+ const backoffMs = Math.min(maxBackoffMs, initialBackoffMs * (2 ** Math.max(0, attempts - 1)))
43
+ return {
44
+ attempts,
45
+ lastAttemptAt: now,
46
+ nextRetryAt: now + backoffMs,
47
+ backoffMs,
48
+ error,
49
+ exhausted: attempts >= maxAttempts,
50
+ }
51
+ }
52
+
53
+ export function clearReconnectState(connectorId: string): void {
54
+ connectorReconnectStateStore.delete(connectorId)
55
+ }
56
+
57
+ export function setReconnectState(connectorId: string, state: ConnectorReconnectState): void {
58
+ connectorReconnectStateStore.set(connectorId, state)
59
+ }
60
+
61
+ export function getReconnectState(connectorId: string): ConnectorReconnectState | null {
62
+ return connectorReconnectStateStore.get(connectorId) ?? null
63
+ }
64
+
65
+ export function getAllReconnectStates(): Record<string, ConnectorReconnectState> {
66
+ const out: Record<string, ConnectorReconnectState> = {}
67
+ for (const [id, state] of connectorReconnectStateStore.entries()) {
68
+ out[id] = { ...state }
69
+ }
70
+ return out
71
+ }
@@ -0,0 +1,256 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import { UPLOAD_DIR } from '../storage'
4
+ import { safeJsonParseObject } from '../json-utils'
5
+ import type { InboundMessage, InboundMedia } from './types'
6
+
7
+ function resolveUploadPathFromUrl(rawUrl: string): string | null {
8
+ if (!rawUrl) return null
9
+ const normalized = rawUrl.trim()
10
+ const match = normalized.match(/\/api\/uploads\/([^?#)\s]+)/)
11
+ if (!match) return null
12
+ let decoded: string
13
+ try { decoded = decodeURIComponent(match[1]) } catch { decoded = match[1] }
14
+ const safeName = decoded.replace(/[^a-zA-Z0-9._-]/g, '')
15
+ if (!safeName) return null
16
+ const filePath = path.join(UPLOAD_DIR, safeName)
17
+ return fs.existsSync(filePath) ? filePath : null
18
+ }
19
+
20
+ export function uploadApiUrlFromPath(filePath: string): string | null {
21
+ const rel = path.relative(UPLOAD_DIR, filePath)
22
+ if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return null
23
+ const fileName = path.basename(rel)
24
+ return `/api/uploads/${encodeURIComponent(fileName)}`
25
+ }
26
+
27
+ export function parseSseDataEvents(raw: string): Array<Record<string, unknown>> {
28
+ if (!raw) return []
29
+ const events: Array<Record<string, unknown>> = []
30
+ const lines = raw.split('\n')
31
+ for (const line of lines) {
32
+ if (!line.startsWith('data: ')) continue
33
+ const parsed = safeJsonParseObject(line.slice(6))
34
+ if (parsed) events.push(parsed)
35
+ }
36
+ return events
37
+ }
38
+
39
+ export function parseConnectorToolResult(toolOutput: string): { status?: string; to?: string; followUpId?: string; messageId?: string } | null {
40
+ const record = safeJsonParseObject(toolOutput)
41
+ if (!record) return null
42
+ const status = typeof record.status === 'string' ? String(record.status) : undefined
43
+ const to = typeof record.to === 'string' ? String(record.to) : undefined
44
+ const followUpId = typeof record.followUpId === 'string' ? String(record.followUpId) : undefined
45
+ const messageId = typeof record.messageId === 'string' ? String(record.messageId) : undefined
46
+ return { status, to, followUpId, messageId }
47
+ }
48
+
49
+ export function parseConnectorToolInput(toolInput: string): Record<string, unknown> | null {
50
+ return safeJsonParseObject(toolInput)
51
+ }
52
+
53
+ export function visibleConnectorToolText(input: Record<string, unknown> | null): string {
54
+ if (!input) return ''
55
+ const voiceText = typeof input.voiceText === 'string' ? input.voiceText.trim() : ''
56
+ if (voiceText) return voiceText
57
+ const message = typeof input.message === 'string' ? input.message.trim() : ''
58
+ if (message) return message
59
+ const caption = typeof input.caption === 'string' ? input.caption.trim() : ''
60
+ if (caption) return caption
61
+ const text = typeof input.text === 'string' ? input.text.trim() : ''
62
+ if (text) return text
63
+ return ''
64
+ }
65
+
66
+ function canonicalUploadMediaKey(filePath: string): string {
67
+ const base = path.basename(filePath)
68
+ const ext = path.extname(base).toLowerCase()
69
+ const normalized = base
70
+ .replace(/^\d{10,16}-/, '')
71
+ .replace(/^(?:browser|screenshot)-\d{10,16}(?:-\d+)?\./, 'playwright-capture.')
72
+ .toLowerCase()
73
+ return normalized || `unknown${ext}`
74
+ }
75
+
76
+ function shouldAllowMultipleMediaSends(userText: string): boolean {
77
+ const text = (userText || '').toLowerCase()
78
+ return /\b(all|both|multiple|several|many|every|each|two|three|4|four|screenshots|images|photos|files|documents)\b/.test(text)
79
+ }
80
+
81
+ function preferSingleBestMediaFile(files: Array<{ path: string; alt: string }>): Array<{ path: string; alt: string }> {
82
+ if (files.length <= 1) return files
83
+ const ranked = [...files].sort((a, b) => {
84
+ const score = (entry: { path: string }) => {
85
+ const base = path.basename(entry.path).toLowerCase()
86
+ let value = 0
87
+ if (/^\d{10,16}-/.test(base)) value += 20
88
+ if (!base.startsWith('browser-') && !base.startsWith('screenshot-')) value += 10
89
+ if (base.endsWith('.pdf')) value += 8
90
+ if (base.endsWith('.png') || base.endsWith('.jpg') || base.endsWith('.jpeg') || base.endsWith('.webp')) value += 6
91
+ try {
92
+ const stat = fs.statSync(entry.path)
93
+ value += Math.min(5, Math.round((stat.mtimeMs % 10_000) / 2_000))
94
+ } catch { /* ignore stat errors */ }
95
+ return value
96
+ }
97
+ return score(b) - score(a)
98
+ })
99
+ return [ranked[0]]
100
+ }
101
+
102
+ export function selectOutboundMediaFiles(
103
+ files: Array<{ path: string; alt: string }>,
104
+ userText: string,
105
+ ): Array<{ path: string; alt: string }> {
106
+ if (files.length === 0) return []
107
+ const mergedFiles: Array<{ path: string; alt: string }> = []
108
+ const seenMediaKeys = new Set<string>()
109
+ for (const candidate of files) {
110
+ const mediaKey = canonicalUploadMediaKey(candidate.path)
111
+ if (seenMediaKeys.has(mediaKey)) continue
112
+ seenMediaKeys.add(mediaKey)
113
+ mergedFiles.push(candidate)
114
+ }
115
+ return shouldAllowMultipleMediaSends(userText || '')
116
+ ? mergedFiles
117
+ : preferSingleBestMediaFile(mergedFiles)
118
+ }
119
+
120
+ export function extractEmbeddedMedia(text: string): { cleanText: string; files: Array<{ path: string; alt: string }> } {
121
+ const files: Array<{ path: string; alt: string }> = []
122
+ const seen = new Set<string>()
123
+ let cleanText = text
124
+
125
+ const pushFile = (filePath: string, alt: string) => {
126
+ if (!filePath || seen.has(filePath)) return
127
+ seen.add(filePath)
128
+ files.push({ path: filePath, alt: alt.trim() })
129
+ }
130
+
131
+ const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g
132
+ cleanText = cleanText.replace(imageRegex, (full, altRaw, urlRaw) => {
133
+ const filePath = resolveUploadPathFromUrl(String(urlRaw || ''))
134
+ if (!filePath) return full
135
+ pushFile(filePath, String(altRaw || ''))
136
+ return ''
137
+ })
138
+
139
+ const linkRegex = /(?<!!)\[([^\]]*)\]\(([^)]+)\)/g
140
+ cleanText = cleanText.replace(linkRegex, (full, altRaw, urlRaw) => {
141
+ const filePath = resolveUploadPathFromUrl(String(urlRaw || ''))
142
+ if (!filePath) return full
143
+ pushFile(filePath, String(altRaw || ''))
144
+ return ''
145
+ })
146
+
147
+ const bareUploadUrlRegex = /(?:https?:\/\/[^\s)]+)?\/api\/uploads\/[^\s)\]]+/g
148
+ cleanText = cleanText.replace(bareUploadUrlRegex, (full) => {
149
+ const filePath = resolveUploadPathFromUrl(full)
150
+ if (!filePath) return full
151
+ pushFile(filePath, '')
152
+ return ''
153
+ })
154
+
155
+ if (files.length === 0) return { cleanText: text, files }
156
+ cleanText = cleanText.replace(/\n{3,}/g, '\n\n').trim()
157
+ return { cleanText, files }
158
+ }
159
+
160
+ export function buildInboundAttachmentPaths(msg: InboundMessage): string[] {
161
+ if (!Array.isArray(msg.media) || msg.media.length === 0) return []
162
+ const paths: string[] = []
163
+ const seen = new Set<string>()
164
+ for (const media of msg.media) {
165
+ const localPath = typeof media.localPath === 'string' ? media.localPath.trim() : ''
166
+ if (!localPath || seen.has(localPath)) continue
167
+ if (!fs.existsSync(localPath)) continue
168
+ seen.add(localPath)
169
+ paths.push(localPath)
170
+ }
171
+ return paths
172
+ }
173
+
174
+ /**
175
+ * Normalize a phone number string to E.164 format (+<digits>).
176
+ * Strips formatting characters, `whatsapp:` prefixes, and ensures a leading `+`.
177
+ * Works for all country codes — no country-specific heuristics.
178
+ */
179
+ export function normalizeE164(number: string): string {
180
+ const withoutPrefix = number.replace(/^whatsapp:/i, '').trim()
181
+ const digits = withoutPrefix.replace(/[^\d+]/g, '')
182
+ if (digits.startsWith('+')) return `+${digits.slice(1)}`
183
+ return `+${digits}`
184
+ }
185
+
186
+ const WHATSAPP_USER_JID_RE = /^(\d+)(?::\d+)?@s\.whatsapp\.net$/i
187
+ const WHATSAPP_LID_RE = /^(\d+)(?::\d+)?@lid$/i
188
+ const WHATSAPP_GROUP_JID_RE = /^[\d]+(-[\d]+)*@g\.us$/i
189
+
190
+ /**
191
+ * Normalize a WhatsApp target (phone number, user JID, group JID) into
192
+ * a canonical JID suitable for sending messages.
193
+ *
194
+ * - Group JIDs (`…@g.us`) are preserved as-is.
195
+ * - User JIDs (`…@s.whatsapp.net`, `…@lid`) extract the phone number.
196
+ * - Plain phone numbers are cleaned to digits and suffixed with `@s.whatsapp.net`.
197
+ *
198
+ * Works for all country codes — ported from OpenClaw's normalizeWhatsAppTarget.
199
+ */
200
+ export function normalizeWhatsappTarget(raw: string): string {
201
+ const trimmed = raw.replace(/^whatsapp:/i, '').trim()
202
+ if (!trimmed) return trimmed
203
+
204
+ // Group JIDs — preserve as-is
205
+ if (WHATSAPP_GROUP_JID_RE.test(trimmed)) return trimmed
206
+
207
+ // User JIDs — extract the phone number digits
208
+ const userMatch = trimmed.match(WHATSAPP_USER_JID_RE)
209
+ if (userMatch) return `${userMatch[1]}@s.whatsapp.net`
210
+
211
+ const lidMatch = trimmed.match(WHATSAPP_LID_RE)
212
+ if (lidMatch) return trimmed // LID JIDs can't be converted to phone-based JIDs
213
+
214
+ // Unknown JID format — return as-is to avoid mangling
215
+ if (trimmed.includes('@')) return trimmed
216
+
217
+ // Plain phone number — strip to digits and build JID
218
+ const digits = trimmed.replace(/[^\d+]/g, '')
219
+ const cleaned = digits.startsWith('+') ? digits.slice(1) : digits
220
+ return cleaned ? `${cleaned}@s.whatsapp.net` : trimmed
221
+ }
222
+
223
+ export function connectorSupportsBinaryMedia(platform: string): boolean {
224
+ return platform === 'whatsapp'
225
+ || platform === 'telegram'
226
+ || platform === 'slack'
227
+ || platform === 'discord'
228
+ || platform === 'openclaw'
229
+ }
230
+
231
+ export function formatMediaLine(media: InboundMedia): string {
232
+ const typeLabel = media.type.toUpperCase()
233
+ const name = media.fileName || media.mimeType || 'attachment'
234
+ const size = media.sizeBytes ? ` (${Math.max(1, Math.round(media.sizeBytes / 1024))} KB)` : ''
235
+ if (media.url) return `- ${typeLabel}: ${name}${size} -> ${media.url}`
236
+ return `- ${typeLabel}: ${name}${size}`
237
+ }
238
+
239
+ export function formatInboundUserText(msg: InboundMessage): string {
240
+ const baseText = (msg.text || '').trim()
241
+ const lines: string[] = []
242
+ if (baseText) lines.push(`[${msg.senderName}] ${baseText}`)
243
+ else lines.push(`[${msg.senderName}]`)
244
+
245
+ if (Array.isArray(msg.media) && msg.media.length > 0) {
246
+ lines.push('')
247
+ lines.push('Media received:')
248
+ const preview = msg.media.slice(0, 6)
249
+ for (const media of preview) lines.push(formatMediaLine(media))
250
+ if (msg.media.length > preview.length) {
251
+ lines.push(`- ...and ${msg.media.length - preview.length} more attachment(s)`)
252
+ }
253
+ }
254
+
255
+ return lines.join('\n').trim()
256
+ }
@@ -0,0 +1,67 @@
1
+ import type { Connector, Session } from '@/types'
2
+ import type { ConnectorInstance, InboundMessage } from './types'
3
+ import { hmrSingleton } from '@/lib/shared-utils'
4
+
5
+ export interface ConnectorReconnectState {
6
+ attempts: number
7
+ lastAttemptAt: number
8
+ nextRetryAt: number
9
+ backoffMs: number
10
+ error: string
11
+ exhausted: boolean
12
+ }
13
+
14
+ export interface ScheduledConnectorFollowup {
15
+ id: string
16
+ connectorId?: string
17
+ platform?: string
18
+ channelId: string
19
+ sendAt: number
20
+ timer: ReturnType<typeof setTimeout>
21
+ }
22
+
23
+ export interface DebouncedInboundEntry {
24
+ connector: Connector
25
+ messages: InboundMessage[]
26
+ timer: ReturnType<typeof setTimeout>
27
+ }
28
+
29
+ export type RouteMessageHandler = (connector: Connector, msg: InboundMessage) => Promise<string>
30
+
31
+ export interface ConnectorRuntimeState {
32
+ running: Map<string, ConnectorInstance>
33
+ lastInboundChannelByConnector: Map<string, string>
34
+ lastInboundTimeByConnector: Map<string, number>
35
+ locks: Map<string, Promise<void>>
36
+ generationCounter: Map<string, number>
37
+ scheduledFollowups: Map<string, ScheduledConnectorFollowup>
38
+ recentInboundByKey: Map<string, number>
39
+ pendingInboundDebounce: Map<string, DebouncedInboundEntry>
40
+ scheduledFollowupByDedupe: Map<string, { id: string; sendAt: number }>
41
+ reconnectStates: Map<string, ConnectorReconnectState>
42
+ routeMessageHandlerRef: { current: RouteMessageHandler }
43
+ }
44
+
45
+ export function getConnectorRuntimeState(): ConnectorRuntimeState {
46
+ return hmrSingleton('__swarmclaw_connector_runtime_state__', () => ({
47
+ running: hmrSingleton('__swarmclaw_running_connectors__', () => new Map<string, ConnectorInstance>()),
48
+ lastInboundChannelByConnector: hmrSingleton('__swarmclaw_connector_last_inbound__', () => new Map<string, string>()),
49
+ lastInboundTimeByConnector: hmrSingleton('__swarmclaw_connector_last_inbound_time__', () => new Map<string, number>()),
50
+ locks: hmrSingleton('__swarmclaw_connector_locks__', () => new Map<string, Promise<void>>()),
51
+ generationCounter: hmrSingleton('__swarmclaw_connector_gen__', () => new Map<string, number>()),
52
+ scheduledFollowups: hmrSingleton('__swarmclaw_connector_followups__', () => new Map<string, ScheduledConnectorFollowup>()),
53
+ recentInboundByKey: hmrSingleton('__swarmclaw_connector_inbound_dedupe__', () => new Map<string, number>()),
54
+ pendingInboundDebounce: hmrSingleton('__swarmclaw_connector_inbound_debounce__', () => new Map<string, DebouncedInboundEntry>()),
55
+ scheduledFollowupByDedupe: hmrSingleton('__swarmclaw_connector_followup_dedupe__', () => new Map<string, { id: string; sendAt: number }>()),
56
+ reconnectStates: hmrSingleton('__swarmclaw_connector_reconnect_state__', () => new Map<string, ConnectorReconnectState>()),
57
+ routeMessageHandlerRef: hmrSingleton('__swarmclaw_connector_route_handler__', () => ({
58
+ current: async () => '[Error] Connector router unavailable.',
59
+ })),
60
+ }))
61
+ }
62
+
63
+ export const connectorRuntimeState = getConnectorRuntimeState()
64
+
65
+ export const runningConnectors = connectorRuntimeState.running
66
+
67
+ export type ConnectorThreadSession = Session