@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
@@ -0,0 +1,533 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import type { Message, MessageToolEvent } from '@/types'
4
+ import { dedup } from '@/lib/shared-utils'
5
+ import { getUsageSpendSince } from './storage'
6
+ import { pluginIdMatches } from './tool-aliases'
7
+ import { buildToolEventAssistantSummary } from '@/lib/tool-event-summary'
8
+
9
+ export interface SessionWithTools {
10
+ plugins?: string[] | null
11
+ /** @deprecated Use plugins */
12
+ tools?: string[] | null
13
+ }
14
+
15
+ export type DelegateTool =
16
+ | 'delegate_to_claude_code'
17
+ | 'delegate_to_codex_cli'
18
+ | 'delegate_to_opencode_cli'
19
+ | 'delegate_to_gemini_cli'
20
+
21
+ export function applyContextClearBoundary(messages: Message[]): Message[] {
22
+ const filterModelHistory = (items: Message[]) => items.filter((message) => message.historyExcluded !== true)
23
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
24
+ if (messages[i].kind === 'context-clear') return filterModelHistory(messages.slice(i + 1))
25
+ }
26
+ return filterModelHistory(messages)
27
+ }
28
+
29
+ export function shouldApplySessionFreshnessReset(source: string): boolean {
30
+ return source !== 'eval'
31
+ }
32
+
33
+ export function shouldAutoRouteHeartbeatAlerts(config?: {
34
+ showAlerts?: boolean
35
+ deliveryMode?: 'default' | 'tool_only'
36
+ } | null): boolean {
37
+ if (config?.showAlerts === false) return false
38
+ return config?.deliveryMode !== 'tool_only'
39
+ }
40
+
41
+ export function shouldPersistInboundUserMessage(internal: boolean, source: string): boolean {
42
+ if (!internal) return true
43
+ return source === 'eval'
44
+ }
45
+
46
+ function escapeRegExp(value: string): string {
47
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
48
+ }
49
+
50
+ function hasExplicitToolMention(message: string, toolName: string): boolean {
51
+ const escaped = escapeRegExp(toolName)
52
+ const negated = new RegExp(`\\b(?:do not|don't|dont|avoid|skip|without|never)\\s+(?:use\\s+|call\\s+|invoke\\s+)?(?:the\\s+)?\`?${escaped}\`?(?:\\s+tool)?\\b`, 'i')
53
+ if (negated.test(message)) return false
54
+ const boundary = new RegExp(`(^|[^a-z0-9_])\`?${escaped}\`?([^a-z0-9_]|$)`, 'i')
55
+ return boundary.test(message)
56
+ }
57
+
58
+ function hasExplicitGenericToolRequest(message: string, toolName: string): boolean {
59
+ const escaped = escapeRegExp(toolName)
60
+ const negated = new RegExp(`\\b(?:do not|don't|dont|avoid|skip|without|never)\\s+(?:use\\s+|call\\s+|invoke\\s+)?(?:the\\s+)?${escaped}(?:\\s+tool)?\\b`, 'i')
61
+ if (negated.test(message)) return false
62
+ return new RegExp(`(^|[\\s(])\`${escaped}\`([\\s).,!?]|$)|\\b${escaped}\\s+tool\\b|\\buse\\s+(?:the\\s+)?${escaped}\\b|\\bcall\\s+(?:the\\s+)?${escaped}\\b|\\binvoke\\s+(?:the\\s+)?${escaped}\\b`, 'i').test(message)
63
+ }
64
+
65
+ const MANAGE_PLATFORM_RESOURCE_TO_TOOL: Record<string, string> = {
66
+ agent: 'manage_agents',
67
+ agents: 'manage_agents',
68
+ project: 'manage_projects',
69
+ projects: 'manage_projects',
70
+ task: 'manage_tasks',
71
+ tasks: 'manage_tasks',
72
+ schedule: 'manage_schedules',
73
+ schedules: 'manage_schedules',
74
+ skill: 'manage_skills',
75
+ skills: 'manage_skills',
76
+ document: 'manage_documents',
77
+ documents: 'manage_documents',
78
+ secret: 'manage_secrets',
79
+ secrets: 'manage_secrets',
80
+ connector: 'manage_connectors',
81
+ connectors: 'manage_connectors',
82
+ session: 'manage_sessions',
83
+ sessions: 'manage_sessions',
84
+ }
85
+
86
+ export function translateRequestedToolInvocation(
87
+ requestedName: string,
88
+ rawArgs: Record<string, unknown>,
89
+ messageFallback: string,
90
+ availableToolNames?: Iterable<string>,
91
+ ): { toolName: string; args: Record<string, unknown> } {
92
+ const available = new Set(availableToolNames || [])
93
+
94
+ if (requestedName === 'web_search') {
95
+ return {
96
+ toolName: 'web',
97
+ args: {
98
+ action: 'search',
99
+ query: typeof rawArgs.query === 'string' ? rawArgs.query : messageFallback.trim(),
100
+ maxResults: typeof rawArgs.maxResults === 'number' ? rawArgs.maxResults : 5,
101
+ },
102
+ }
103
+ }
104
+ if (requestedName === 'web_fetch') {
105
+ return {
106
+ toolName: 'web',
107
+ args: {
108
+ action: 'fetch',
109
+ url: rawArgs.url,
110
+ },
111
+ }
112
+ }
113
+ if (requestedName === 'delegate_to_claude_code') {
114
+ return { toolName: 'delegate', args: { ...rawArgs, backend: 'claude' } }
115
+ }
116
+ if (requestedName === 'delegate_to_codex_cli') {
117
+ return { toolName: 'delegate', args: { ...rawArgs, backend: 'codex' } }
118
+ }
119
+ if (requestedName === 'delegate_to_opencode_cli') {
120
+ return { toolName: 'delegate', args: { ...rawArgs, backend: 'opencode' } }
121
+ }
122
+ if (requestedName === 'delegate_to_gemini_cli') {
123
+ return { toolName: 'delegate', args: { ...rawArgs, backend: 'gemini' } }
124
+ }
125
+
126
+ const managePrefix = 'manage_'
127
+ if (requestedName === 'manage_platform') {
128
+ const resource = typeof rawArgs.resource === 'string'
129
+ ? rawArgs.resource.trim().toLowerCase()
130
+ : ''
131
+ const specificTool = MANAGE_PLATFORM_RESOURCE_TO_TOOL[resource]
132
+ if (specificTool && available.has(specificTool) && !available.has('manage_platform')) {
133
+ return { toolName: specificTool, args: rawArgs }
134
+ }
135
+ return { toolName: requestedName, args: rawArgs }
136
+ }
137
+
138
+ if (requestedName.startsWith(managePrefix) && requestedName !== 'manage_platform') {
139
+ if (!available.has(requestedName) && available.has('manage_platform')) {
140
+ const resource = requestedName.slice(managePrefix.length)
141
+ if (resource) {
142
+ const { action, id, data, ...rest } = rawArgs
143
+ const nextArgs: Record<string, unknown> = { resource, ...rest }
144
+ if (action !== undefined) nextArgs.action = action
145
+ if (id !== undefined) nextArgs.id = id
146
+ if (data !== undefined) nextArgs.data = data
147
+ return {
148
+ toolName: 'manage_platform',
149
+ args: nextArgs,
150
+ }
151
+ }
152
+ }
153
+ return { toolName: requestedName, args: rawArgs }
154
+ }
155
+
156
+ return { toolName: requestedName, args: rawArgs }
157
+ }
158
+
159
+ function normalizeWorkspaceSandboxLinks(text: string, cwd: string): string {
160
+ return text.replace(/\[([^\]]+)\]\(sandbox:\/workspace\/([^)]+)\)/g, (raw, label: string, relativePath: string) => {
161
+ const normalized = String(relativePath || '').replace(/^\/+/, '')
162
+ if (!normalized) return raw
163
+ const resolvedCwd = path.resolve(cwd)
164
+ const resolved = path.resolve(resolvedCwd, normalized)
165
+ if (!resolved.startsWith(resolvedCwd)) return raw
166
+ if (!fs.existsSync(resolved)) return raw
167
+ return `[${label}](/api/files/serve?path=${encodeURIComponent(resolved)})`
168
+ })
169
+ }
170
+
171
+ function normalizeAbsoluteFileMarkdownLinks(text: string): string {
172
+ return text.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (raw, label: string, target: string) => {
173
+ if (!path.isAbsolute(target)) return raw
174
+ const resolved = path.resolve(target)
175
+ if (!fs.existsSync(resolved)) return raw
176
+ return `[${label}](/api/files/serve?path=${encodeURIComponent(resolved)})`
177
+ })
178
+ }
179
+
180
+ export function normalizeAssistantArtifactLinks(text: string, cwd: string): string {
181
+ const uploadsNormalized = text.replace(/sandbox:\/api\/uploads\//g, '/api/uploads/')
182
+ const workspaceNormalized = normalizeWorkspaceSandboxLinks(uploadsNormalized, cwd)
183
+ return normalizeAbsoluteFileMarkdownLinks(workspaceNormalized)
184
+ }
185
+
186
+ export function extractHeartbeatStatus(text: string): { goal?: string; status?: string; summary?: string; nextAction?: string } | null {
187
+ const match = text.match(/\[AGENT_HEARTBEAT_META\]\s*(\{[^\n]*\})/i)
188
+ if (!match) return null
189
+ try {
190
+ const meta = JSON.parse(match[1]) as Record<string, unknown>
191
+ const payload: { goal?: string; status?: string; summary?: string; nextAction?: string } = {}
192
+ if (typeof meta.goal === 'string' && meta.goal.trim()) payload.goal = meta.goal.trim()
193
+ if (typeof meta.status === 'string' && meta.status.trim()) payload.status = meta.status.trim()
194
+ if (typeof meta.summary === 'string' && meta.summary.trim()) payload.summary = meta.summary.trim()
195
+ if (typeof meta.next_action === 'string' && meta.next_action.trim()) payload.nextAction = meta.next_action.trim()
196
+ return Object.keys(payload).length > 0 ? payload : null
197
+ } catch {
198
+ return null
199
+ }
200
+ }
201
+
202
+ export function shouldReplaceRecentAssistantMessage(params: {
203
+ previous: Message | null | undefined
204
+ nextToolEvents: MessageToolEvent[]
205
+ nextKind: Message['kind']
206
+ now: number
207
+ }): boolean {
208
+ const { previous, nextToolEvents, nextKind, now } = params
209
+ if (!previous || previous.role !== 'assistant') return false
210
+ if (nextToolEvents.length === 0) return false
211
+ if (previous.kind && nextKind && previous.kind !== nextKind) return false
212
+ if (typeof previous.time === 'number' && now - previous.time > 45_000) return false
213
+ const prevTools = Array.isArray(previous.toolEvents) ? previous.toolEvents.length : 0
214
+ return prevTools === 0
215
+ }
216
+
217
+ export function hasPersistableAssistantPayload(text: string, thinking: string, toolEvents: MessageToolEvent[]): boolean {
218
+ return text.trim().length > 0 || thinking.trim().length > 0 || toolEvents.length > 0
219
+ }
220
+
221
+ export function getPersistedAssistantText(text: string, toolEvents: MessageToolEvent[]): string {
222
+ const trimmed = text.trim()
223
+ if (trimmed) return trimmed
224
+ return buildToolEventAssistantSummary(toolEvents)
225
+ }
226
+
227
+ export function getToolEventsSnapshotKey(toolEvents: MessageToolEvent[]): string {
228
+ return JSON.stringify(toolEvents.map((event) => [
229
+ event.name,
230
+ event.input,
231
+ event.output || '',
232
+ event.error === true,
233
+ event.toolCallId || '',
234
+ ]))
235
+ }
236
+
237
+ export function requestedToolNamesFromMessage(message: string): string[] {
238
+ const explicitCandidates = [
239
+ 'delegate_to_claude_code',
240
+ 'delegate_to_codex_cli',
241
+ 'delegate_to_opencode_cli',
242
+ 'delegate_to_gemini_cli',
243
+ 'connector_message_tool',
244
+ 'sessions_tool',
245
+ 'whoami_tool',
246
+ 'search_history_tool',
247
+ 'manage_agents',
248
+ 'manage_tasks',
249
+ 'manage_schedules',
250
+ 'manage_documents',
251
+ 'manage_webhooks',
252
+ 'manage_skills',
253
+ 'manage_connectors',
254
+ 'manage_sessions',
255
+ 'manage_secrets',
256
+ 'manage_capabilities',
257
+ 'manage_platform',
258
+ 'manage_chatrooms',
259
+ 'search_marketplace',
260
+ 'monitor_tool',
261
+ 'plugin_creator_tool',
262
+ 'memory_tool',
263
+ 'memory_search',
264
+ 'memory_get',
265
+ 'memory_store',
266
+ 'memory_update',
267
+ 'wallet_tool',
268
+ 'http_request',
269
+ 'send_file',
270
+ 'sandbox_exec',
271
+ 'sandbox_list_runtimes',
272
+ 'schedule_wake',
273
+ 'spawn_subagent',
274
+ 'ask_human',
275
+ 'context_status',
276
+ 'context_summarize',
277
+ 'openclaw_nodes',
278
+ 'openclaw_workspace',
279
+ ]
280
+ const genericCandidates = [
281
+ 'browser',
282
+ 'web',
283
+ 'shell',
284
+ 'files',
285
+ 'edit_file',
286
+ 'git',
287
+ 'canvas',
288
+ 'mailbox',
289
+ 'document',
290
+ 'extract',
291
+ 'table',
292
+ 'crawl',
293
+ 'email',
294
+ ]
295
+ const requested = explicitCandidates.filter((name) => hasExplicitToolMention(message, name))
296
+ for (const name of genericCandidates) {
297
+ if (hasExplicitGenericToolRequest(message, name)) requested.push(name)
298
+ }
299
+ if (hasExplicitGenericToolRequest(message, 'delegate')) {
300
+ requested.push('delegate')
301
+ }
302
+ return dedup(requested)
303
+ }
304
+
305
+ export function hasToolEnabled(session: SessionWithTools, toolName: string): boolean {
306
+ return pluginIdMatches(session?.plugins || session?.tools || [], toolName)
307
+ }
308
+
309
+ export function enabledDelegationTools(session: SessionWithTools): DelegateTool[] {
310
+ const tools: DelegateTool[] = []
311
+ if (hasToolEnabled(session, 'claude_code') || hasToolEnabled(session, 'delegate')) tools.push('delegate_to_claude_code')
312
+ if (hasToolEnabled(session, 'codex_cli')) tools.push('delegate_to_codex_cli')
313
+ if (hasToolEnabled(session, 'opencode_cli')) tools.push('delegate_to_opencode_cli')
314
+ if (hasToolEnabled(session, 'gemini_cli')) tools.push('delegate_to_gemini_cli')
315
+ return tools
316
+ }
317
+
318
+ export function hasDirectLocalCodingTools(session: SessionWithTools): boolean {
319
+ return [
320
+ 'shell',
321
+ 'execute_command',
322
+ 'files',
323
+ 'edit_file',
324
+ 'openclaw_workspace',
325
+ 'sandbox',
326
+ ].some((toolName) => hasToolEnabled(session, toolName))
327
+ }
328
+
329
+ export function parseUsdLimit(value: unknown): number | null {
330
+ const parsed = typeof value === 'number'
331
+ ? value
332
+ : typeof value === 'string'
333
+ ? Number.parseFloat(value)
334
+ : Number.NaN
335
+ if (!Number.isFinite(parsed) || parsed <= 0) return null
336
+ return Math.max(0.01, Math.min(1_000_000, parsed))
337
+ }
338
+
339
+ export function getTodaySpendUsd(): number {
340
+ const dayStart = new Date()
341
+ dayStart.setHours(0, 0, 0, 0)
342
+ return getUsageSpendSince(dayStart.getTime())
343
+ }
344
+
345
+ export function findFirstUrl(text: string): string | null {
346
+ const match = text.match(/https?:\/\/[^\s<>"')]+/i)
347
+ return match?.[0] || null
348
+ }
349
+
350
+ export function isMemoryListIntent(message: string): boolean {
351
+ const text = message.toLowerCase()
352
+ if (!/\bmemory|memories|remember\b/.test(text)) return false
353
+ if (/\b(save|store|memorize|add to memory|write to memory|remember this)\b/.test(text)) return false
354
+ if (/\bmemory_tool\b/.test(text)) return true
355
+ return (
356
+ /\blist\b[\s\w]{0,24}\bmemories\b/.test(text)
357
+ || /\bshow\b[\s\w]{0,24}\bmemories\b/.test(text)
358
+ || /\bget\b[\s\w]{0,24}\bmemories\b/.test(text)
359
+ || /\bwhat\b[\s\w]{0,40}\bmemories\b/.test(text)
360
+ || /\bwhat do you remember\b/.test(text)
361
+ || /\brecall\b[\s\w]{0,24}\bmemories?\b/.test(text)
362
+ )
363
+ }
364
+
365
+ export function extractDelegationTask(message: string, toolName: string): string | null {
366
+ if (!message.toLowerCase().includes(toolName.toLowerCase())) return null
367
+ const patterns = [
368
+ /task\s+exactly\s*:\s*"([^"]+)"/i,
369
+ /task\s+exactly\s*:\s*'([^']+)'/i,
370
+ /task\s+exactly\s*:\s*([^\n]+?)(?:\.\s|$)/i,
371
+ /task\s*:\s*"([^"]+)"/i,
372
+ /task\s*:\s*'([^']+)'/i,
373
+ /task\s*:\s*([^\n]+?)(?:\.\s|$)/i,
374
+ ]
375
+ for (const re of patterns) {
376
+ const match = message.match(re)
377
+ const task = (match?.[1] || '').trim()
378
+ if (task) return task
379
+ }
380
+ return null
381
+ }
382
+
383
+ function parseKeyValueArgs(raw: string): Record<string, string> {
384
+ const out: Record<string, string> = {}
385
+ const regex = /([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*("([^"]*)"|'([^']*)'|[^\s,]+)/g
386
+ let match: RegExpExecArray | null = null
387
+ while ((match = regex.exec(raw)) !== null) {
388
+ const key = match[1]
389
+ const value = match[3] ?? match[4] ?? match[2] ?? ''
390
+ out[key] = value.replace(/^['"]|['"]$/g, '').trim()
391
+ }
392
+ return out
393
+ }
394
+
395
+ export function extractConnectorMessageArgs(message: string): {
396
+ action:
397
+ | 'list_running'
398
+ | 'list_targets'
399
+ | 'start'
400
+ | 'stop'
401
+ | 'send'
402
+ | 'send_voice_note'
403
+ | 'schedule_followup'
404
+ platform?: string
405
+ connectorId?: string
406
+ to?: string
407
+ message?: string
408
+ voiceText?: string
409
+ voiceId?: string
410
+ imageUrl?: string
411
+ fileUrl?: string
412
+ mediaPath?: string
413
+ mimeType?: string
414
+ fileName?: string
415
+ caption?: string
416
+ delaySec?: number
417
+ followUpMessage?: string
418
+ followUpDelaySec?: number
419
+ ptt?: boolean
420
+ approved?: boolean
421
+ } | null {
422
+ if (!message.toLowerCase().includes('connector_message_tool')) return null
423
+ const parsed = parseKeyValueArgs(message)
424
+
425
+ let payload = parsed.message
426
+ if (!payload) {
427
+ const quoted = message.match(/message\s*=\s*("(.*?)"|'(.*?)')/i)
428
+ if (quoted) payload = (quoted[2] || quoted[3] || '').trim()
429
+ }
430
+ if (!payload) {
431
+ const raw = message.match(/message\s*=\s*([^\n]+)/i)
432
+ if (raw?.[1]) {
433
+ payload = raw[1]
434
+ .replace(/\b(Return|Output|Then|Respond)\b[\s\S]*$/i, '')
435
+ .trim()
436
+ .replace(/^['"]|['"]$/g, '')
437
+ }
438
+ }
439
+
440
+ const actionRaw = (parsed.action || 'send').toLowerCase()
441
+ const action = (
442
+ actionRaw === 'list_running'
443
+ || actionRaw === 'list_targets'
444
+ || actionRaw === 'start'
445
+ || actionRaw === 'stop'
446
+ || actionRaw === 'send'
447
+ || actionRaw === 'send_voice_note'
448
+ || actionRaw === 'schedule_followup'
449
+ )
450
+ ? actionRaw
451
+ : 'send'
452
+ const args: {
453
+ action:
454
+ | 'list_running'
455
+ | 'list_targets'
456
+ | 'start'
457
+ | 'stop'
458
+ | 'send'
459
+ | 'send_voice_note'
460
+ | 'schedule_followup'
461
+ platform?: string
462
+ connectorId?: string
463
+ to?: string
464
+ message?: string
465
+ voiceText?: string
466
+ voiceId?: string
467
+ imageUrl?: string
468
+ fileUrl?: string
469
+ mediaPath?: string
470
+ mimeType?: string
471
+ fileName?: string
472
+ caption?: string
473
+ delaySec?: number
474
+ followUpMessage?: string
475
+ followUpDelaySec?: number
476
+ ptt?: boolean
477
+ approved?: boolean
478
+ } = { action }
479
+ const quoted = (key: string): string | undefined => {
480
+ const match = message.match(new RegExp(`${key}\\s*=\\s*(\"([^\"]*)\"|'([^']*)')`, 'i'))
481
+ return (match?.[2] || match?.[3] || '').trim() || undefined
482
+ }
483
+ if (parsed.platform) args.platform = parsed.platform
484
+ if (parsed.connectorId) args.connectorId = parsed.connectorId
485
+ if (parsed.to) args.to = parsed.to
486
+ if (payload) args.message = payload
487
+ if (parsed.voiceText) args.voiceText = parsed.voiceText
488
+ if (parsed.voiceId) args.voiceId = parsed.voiceId
489
+ args.imageUrl = parsed.imageUrl || quoted('imageUrl')
490
+ args.fileUrl = parsed.fileUrl || quoted('fileUrl')
491
+ args.mediaPath = parsed.mediaPath || quoted('mediaPath')
492
+ args.mimeType = parsed.mimeType || quoted('mimeType')
493
+ args.fileName = parsed.fileName || quoted('fileName')
494
+ args.caption = parsed.caption || quoted('caption')
495
+ if (parsed.followUpMessage) args.followUpMessage = parsed.followUpMessage
496
+ if (parsed.delaySec && Number.isFinite(Number(parsed.delaySec))) args.delaySec = Number(parsed.delaySec)
497
+ if (parsed.followUpDelaySec && Number.isFinite(Number(parsed.followUpDelaySec))) args.followUpDelaySec = Number(parsed.followUpDelaySec)
498
+ if (parsed.ptt) args.ptt = ['true', '1', 'yes', 'on'].includes(parsed.ptt.toLowerCase())
499
+ if (parsed.approved) args.approved = ['true', '1', 'yes', 'on'].includes(parsed.approved.toLowerCase())
500
+ return args
501
+ }
502
+
503
+ export function stripMarkupForHeartbeat(text: string): string {
504
+ return text
505
+ .replace(/<[^>]*>/g, ' ')
506
+ .replace(/&nbsp;/gi, ' ')
507
+ .replace(/^[*`~_]+/, '')
508
+ .replace(/[*`~_]+$/, '')
509
+ .trim()
510
+ }
511
+
512
+ const HEARTBEAT_OK_RE = /HEARTBEAT_OK[^\w]{0,4}$/
513
+ const NO_MESSAGE_RE = /NO_MESSAGE[^\w]{0,4}$/
514
+
515
+ export function classifyHeartbeatResponse(text: string, ackMaxChars: number, hadToolCalls: boolean): 'suppress' | 'strip' | 'keep' {
516
+ const cleaned = stripMarkupForHeartbeat(text)
517
+ if (cleaned === 'HEARTBEAT_OK' || cleaned === 'NO_MESSAGE') return 'suppress'
518
+ if (HEARTBEAT_OK_RE.test(cleaned) || NO_MESSAGE_RE.test(cleaned)) return 'suppress'
519
+ const stripped = cleaned.replace(/HEARTBEAT_OK/gi, '').replace(/NO_MESSAGE/gi, '').trim()
520
+ if (!stripped) return 'suppress'
521
+ if (!hadToolCalls && stripped.length <= ackMaxChars) return 'suppress'
522
+ return stripped.length < cleaned.length ? 'strip' : 'keep'
523
+ }
524
+
525
+ export function estimateConversationTone(text: string): string {
526
+ const t = text || ''
527
+ if (/```/.test(t) || /\b(function|const|let|var|import|export|class|interface|async|await|return)\b/.test(t)) return 'technical'
528
+ if (/\b(error|bug|debug|stack trace|exception|null|undefined|TypeError)\b/i.test(t)) return 'technical'
529
+ if (/\b(understand|feel|sorry|empathize|appreciate|grateful|tough|difficult|challenging)\b/i.test(t)) return 'empathetic'
530
+ if (/\b(furthermore|regarding|consequently|therefore|henceforth|pursuant|accordingly|notwithstanding)\b/i.test(t)) return 'formal'
531
+ if (/\b(gonna|wanna|gotta|yeah|hey|awesome|cool|lol|btw|tbh)\b/i.test(t) || /!{2,}/.test(t)) return 'casual'
532
+ return 'neutral'
533
+ }