@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,479 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import type { Message, MessageToolEvent } from '@/types'
4
+ import {
5
+ applyContextClearBoundary,
6
+ classifyHeartbeatResponse,
7
+ estimateConversationTone,
8
+ extractHeartbeatStatus,
9
+ getPersistedAssistantText,
10
+ getToolEventsSnapshotKey,
11
+ hasPersistableAssistantPayload,
12
+ parseUsdLimit,
13
+ shouldAutoRouteHeartbeatAlerts,
14
+ shouldPersistInboundUserMessage,
15
+ shouldReplaceRecentAssistantMessage,
16
+ stripMarkupForHeartbeat,
17
+ } from './chat-execution-utils'
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // applyContextClearBoundary
21
+ // ---------------------------------------------------------------------------
22
+ describe('applyContextClearBoundary', () => {
23
+ it('returns all messages when no context-clear marker exists', () => {
24
+ const msgs: Message[] = [
25
+ { role: 'user', text: 'hi', time: 1 },
26
+ { role: 'assistant', text: 'hello', time: 2 },
27
+ ]
28
+ assert.deepEqual(applyContextClearBoundary(msgs), msgs)
29
+ })
30
+
31
+ it('returns messages after the last context-clear marker', () => {
32
+ const msgs: Message[] = [
33
+ { role: 'user', text: 'old', time: 1 },
34
+ { role: 'assistant', text: 'stale', time: 2, kind: 'context-clear' },
35
+ { role: 'user', text: 'fresh', time: 3 },
36
+ { role: 'assistant', text: 'answer', time: 4 },
37
+ ]
38
+ const result = applyContextClearBoundary(msgs)
39
+ assert.equal(result.length, 2)
40
+ assert.equal(result[0].text, 'fresh')
41
+ })
42
+
43
+ it('uses the last context-clear when multiple exist', () => {
44
+ const msgs: Message[] = [
45
+ { role: 'user', text: 'a', time: 1 },
46
+ { role: 'assistant', text: 'x', time: 2, kind: 'context-clear' },
47
+ { role: 'user', text: 'b', time: 3 },
48
+ { role: 'assistant', text: 'y', time: 4, kind: 'context-clear' },
49
+ { role: 'user', text: 'c', time: 5 },
50
+ ]
51
+ const result = applyContextClearBoundary(msgs)
52
+ assert.equal(result.length, 1)
53
+ assert.equal(result[0].text, 'c')
54
+ })
55
+
56
+ it('filters out historyExcluded messages', () => {
57
+ const msgs: Message[] = [
58
+ { role: 'user', text: 'visible', time: 1 },
59
+ { role: 'assistant', text: 'hidden', time: 2, historyExcluded: true },
60
+ { role: 'user', text: 'also visible', time: 3 },
61
+ ]
62
+ const result = applyContextClearBoundary(msgs)
63
+ assert.equal(result.length, 2)
64
+ assert.ok(result.every((m) => m.text !== 'hidden'))
65
+ })
66
+
67
+ it('returns empty array when context-clear is the last message', () => {
68
+ const msgs: Message[] = [
69
+ { role: 'user', text: 'old', time: 1 },
70
+ { role: 'assistant', text: 'clear', time: 2, kind: 'context-clear' },
71
+ ]
72
+ assert.deepEqual(applyContextClearBoundary(msgs), [])
73
+ })
74
+ })
75
+
76
+ // ---------------------------------------------------------------------------
77
+ // shouldPersistInboundUserMessage
78
+ // ---------------------------------------------------------------------------
79
+ describe('shouldPersistInboundUserMessage', () => {
80
+ it('returns true for non-internal messages', () => {
81
+ assert.equal(shouldPersistInboundUserMessage(false, 'chat'), true)
82
+ assert.equal(shouldPersistInboundUserMessage(false, 'connector'), true)
83
+ })
84
+
85
+ it('returns true for internal eval messages', () => {
86
+ assert.equal(shouldPersistInboundUserMessage(true, 'eval'), true)
87
+ })
88
+
89
+ it('returns false for internal non-eval messages', () => {
90
+ assert.equal(shouldPersistInboundUserMessage(true, 'heartbeat'), false)
91
+ assert.equal(shouldPersistInboundUserMessage(true, 'chat'), false)
92
+ assert.equal(shouldPersistInboundUserMessage(true, 'daemon'), false)
93
+ })
94
+ })
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // shouldAutoRouteHeartbeatAlerts
98
+ // ---------------------------------------------------------------------------
99
+ describe('shouldAutoRouteHeartbeatAlerts', () => {
100
+ it('returns true with no config', () => {
101
+ assert.equal(shouldAutoRouteHeartbeatAlerts(), true)
102
+ assert.equal(shouldAutoRouteHeartbeatAlerts(null), true)
103
+ })
104
+
105
+ it('returns false when showAlerts is false', () => {
106
+ assert.equal(shouldAutoRouteHeartbeatAlerts({ showAlerts: false }), false)
107
+ })
108
+
109
+ it('returns false when deliveryMode is tool_only', () => {
110
+ assert.equal(shouldAutoRouteHeartbeatAlerts({ deliveryMode: 'tool_only' }), false)
111
+ })
112
+
113
+ it('returns true for default deliveryMode with showAlerts true', () => {
114
+ assert.equal(shouldAutoRouteHeartbeatAlerts({ showAlerts: true, deliveryMode: 'default' }), true)
115
+ })
116
+ })
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // extractHeartbeatStatus
120
+ // ---------------------------------------------------------------------------
121
+ describe('extractHeartbeatStatus', () => {
122
+ it('extracts meta from AGENT_HEARTBEAT_META tag', () => {
123
+ const text = 'Some text [AGENT_HEARTBEAT_META] {"goal":"monitor","status":"ok","summary":"all good","next_action":"wait"}'
124
+ const result = extractHeartbeatStatus(text)
125
+ assert.deepEqual(result, {
126
+ goal: 'monitor',
127
+ status: 'ok',
128
+ summary: 'all good',
129
+ nextAction: 'wait',
130
+ })
131
+ })
132
+
133
+ it('returns null when no meta tag exists', () => {
134
+ assert.equal(extractHeartbeatStatus('Just a normal response'), null)
135
+ })
136
+
137
+ it('returns null for invalid JSON after the tag', () => {
138
+ assert.equal(extractHeartbeatStatus('[AGENT_HEARTBEAT_META] {invalid json}'), null)
139
+ })
140
+
141
+ it('returns null when JSON has no recognized fields', () => {
142
+ assert.equal(extractHeartbeatStatus('[AGENT_HEARTBEAT_META] {"foo":"bar"}'), null)
143
+ })
144
+
145
+ it('returns partial results when only some fields present', () => {
146
+ const result = extractHeartbeatStatus('[AGENT_HEARTBEAT_META] {"goal":"deploy"}')
147
+ assert.deepEqual(result, { goal: 'deploy' })
148
+ })
149
+
150
+ it('trims whitespace from extracted values', () => {
151
+ const result = extractHeartbeatStatus('[AGENT_HEARTBEAT_META] {"goal":" deploy ","status":" running "}')
152
+ assert.equal(result?.goal, 'deploy')
153
+ assert.equal(result?.status, 'running')
154
+ })
155
+ })
156
+
157
+ // ---------------------------------------------------------------------------
158
+ // shouldReplaceRecentAssistantMessage
159
+ // ---------------------------------------------------------------------------
160
+ describe('shouldReplaceRecentAssistantMessage', () => {
161
+ it('returns false when previous is null', () => {
162
+ assert.equal(shouldReplaceRecentAssistantMessage({
163
+ previous: null,
164
+ nextToolEvents: [{ name: 'shell', input: 'ls' }],
165
+ nextKind: 'chat',
166
+ now: Date.now(),
167
+ }), false)
168
+ })
169
+
170
+ it('returns false when previous is a user message', () => {
171
+ assert.equal(shouldReplaceRecentAssistantMessage({
172
+ previous: { role: 'user', text: 'hi', time: Date.now() },
173
+ nextToolEvents: [{ name: 'shell', input: 'ls' }],
174
+ nextKind: 'chat',
175
+ now: Date.now(),
176
+ }), false)
177
+ })
178
+
179
+ it('returns false when no tool events in new message', () => {
180
+ assert.equal(shouldReplaceRecentAssistantMessage({
181
+ previous: { role: 'assistant', text: 'hi', time: Date.now() },
182
+ nextToolEvents: [],
183
+ nextKind: 'chat',
184
+ now: Date.now(),
185
+ }), false)
186
+ })
187
+
188
+ it('returns false when previous has different kind', () => {
189
+ assert.equal(shouldReplaceRecentAssistantMessage({
190
+ previous: { role: 'assistant', text: 'hi', time: Date.now(), kind: 'heartbeat' },
191
+ nextToolEvents: [{ name: 'shell', input: 'ls' }],
192
+ nextKind: 'chat',
193
+ now: Date.now(),
194
+ }), false)
195
+ })
196
+
197
+ it('returns false when previous message is older than 45 seconds', () => {
198
+ const now = Date.now()
199
+ assert.equal(shouldReplaceRecentAssistantMessage({
200
+ previous: { role: 'assistant', text: 'hi', time: now - 50_000 },
201
+ nextToolEvents: [{ name: 'shell', input: 'ls' }],
202
+ nextKind: 'chat',
203
+ now,
204
+ }), false)
205
+ })
206
+
207
+ it('returns false when previous already has tool events', () => {
208
+ const now = Date.now()
209
+ assert.equal(shouldReplaceRecentAssistantMessage({
210
+ previous: { role: 'assistant', text: 'hi', time: now - 5000, toolEvents: [{ name: 'web', input: '{}' }] },
211
+ nextToolEvents: [{ name: 'shell', input: 'ls' }],
212
+ nextKind: 'chat',
213
+ now,
214
+ }), false)
215
+ })
216
+
217
+ it('returns true when all conditions met: recent assistant, no prev tools, new has tools, same kind', () => {
218
+ const now = Date.now()
219
+ assert.equal(shouldReplaceRecentAssistantMessage({
220
+ previous: { role: 'assistant', text: 'thinking...', time: now - 2000, kind: 'chat' },
221
+ nextToolEvents: [{ name: 'shell', input: 'ls' }],
222
+ nextKind: 'chat',
223
+ now,
224
+ }), true)
225
+ })
226
+
227
+ it('returns true when kinds are both undefined', () => {
228
+ const now = Date.now()
229
+ assert.equal(shouldReplaceRecentAssistantMessage({
230
+ previous: { role: 'assistant', text: 'x', time: now - 1000 },
231
+ nextToolEvents: [{ name: 'shell', input: 'ls' }],
232
+ nextKind: undefined,
233
+ now,
234
+ }), true)
235
+ })
236
+ })
237
+
238
+ // ---------------------------------------------------------------------------
239
+ // hasPersistableAssistantPayload
240
+ // ---------------------------------------------------------------------------
241
+ describe('hasPersistableAssistantPayload', () => {
242
+ it('returns true for non-empty text', () => {
243
+ assert.equal(hasPersistableAssistantPayload('hello', '', []), true)
244
+ })
245
+
246
+ it('returns true for non-empty thinking', () => {
247
+ assert.equal(hasPersistableAssistantPayload('', 'internal thought', []), true)
248
+ })
249
+
250
+ it('returns true when tool events exist', () => {
251
+ assert.equal(hasPersistableAssistantPayload('', '', [{ name: 'shell', input: 'ls' }]), true)
252
+ })
253
+
254
+ it('returns false for all-whitespace text, thinking, and empty events', () => {
255
+ assert.equal(hasPersistableAssistantPayload(' ', ' ', []), false)
256
+ })
257
+
258
+ it('returns false for completely empty inputs', () => {
259
+ assert.equal(hasPersistableAssistantPayload('', '', []), false)
260
+ })
261
+ })
262
+
263
+ // ---------------------------------------------------------------------------
264
+ // getPersistedAssistantText
265
+ // ---------------------------------------------------------------------------
266
+ describe('getPersistedAssistantText', () => {
267
+ it('returns trimmed text when non-empty', () => {
268
+ assert.equal(getPersistedAssistantText(' hello world ', []), 'hello world')
269
+ })
270
+
271
+ it('generates summary from tool events when text is empty', () => {
272
+ const events: MessageToolEvent[] = [
273
+ { name: 'shell', input: 'ls', output: 'files listed' },
274
+ ]
275
+ const result = getPersistedAssistantText('', events)
276
+ assert.ok(result.length > 0)
277
+ assert.ok(result.includes('shell'))
278
+ })
279
+
280
+ it('returns empty string when text is empty and no tool events', () => {
281
+ const result = getPersistedAssistantText('', [])
282
+ // buildToolEventAssistantSummary with empty array returns empty
283
+ assert.equal(result, '')
284
+ })
285
+ })
286
+
287
+ // ---------------------------------------------------------------------------
288
+ // getToolEventsSnapshotKey
289
+ // ---------------------------------------------------------------------------
290
+ describe('getToolEventsSnapshotKey', () => {
291
+ it('returns deterministic key for same events', () => {
292
+ const events: MessageToolEvent[] = [
293
+ { name: 'shell', input: 'ls', output: 'ok', toolCallId: 'c1' },
294
+ ]
295
+ const key1 = getToolEventsSnapshotKey(events)
296
+ const key2 = getToolEventsSnapshotKey([...events])
297
+ assert.equal(key1, key2)
298
+ })
299
+
300
+ it('returns different keys for different events', () => {
301
+ const a = getToolEventsSnapshotKey([{ name: 'shell', input: 'ls' }])
302
+ const b = getToolEventsSnapshotKey([{ name: 'shell', input: 'pwd' }])
303
+ assert.notEqual(a, b)
304
+ })
305
+
306
+ it('returns valid JSON string', () => {
307
+ const key = getToolEventsSnapshotKey([{ name: 'web', input: '{}' }])
308
+ assert.doesNotThrow(() => JSON.parse(key))
309
+ })
310
+
311
+ it('handles error flag correctly', () => {
312
+ const withError = getToolEventsSnapshotKey([{ name: 'shell', input: 'x', error: true }])
313
+ const withoutError = getToolEventsSnapshotKey([{ name: 'shell', input: 'x' }])
314
+ assert.notEqual(withError, withoutError)
315
+ })
316
+
317
+ it('returns stable key for empty array', () => {
318
+ assert.equal(getToolEventsSnapshotKey([]), '[]')
319
+ })
320
+ })
321
+
322
+ // ---------------------------------------------------------------------------
323
+ // parseUsdLimit
324
+ // ---------------------------------------------------------------------------
325
+ describe('parseUsdLimit', () => {
326
+ it('parses a number directly', () => {
327
+ assert.equal(parseUsdLimit(5.0), 5.0)
328
+ })
329
+
330
+ it('parses a numeric string', () => {
331
+ assert.equal(parseUsdLimit('10.50'), 10.50)
332
+ })
333
+
334
+ it('returns null for zero', () => {
335
+ assert.equal(parseUsdLimit(0), null)
336
+ })
337
+
338
+ it('returns null for negative', () => {
339
+ assert.equal(parseUsdLimit(-1), null)
340
+ })
341
+
342
+ it('returns null for NaN', () => {
343
+ assert.equal(parseUsdLimit(Number.NaN), null)
344
+ })
345
+
346
+ it('returns null for non-numeric string', () => {
347
+ assert.equal(parseUsdLimit('not-a-number'), null)
348
+ })
349
+
350
+ it('returns null for null/undefined', () => {
351
+ assert.equal(parseUsdLimit(null), null)
352
+ assert.equal(parseUsdLimit(undefined), null)
353
+ })
354
+
355
+ it('clamps to minimum of 0.01', () => {
356
+ assert.equal(parseUsdLimit(0.001), 0.01)
357
+ })
358
+
359
+ it('clamps to maximum of 1,000,000', () => {
360
+ assert.equal(parseUsdLimit(2_000_000), 1_000_000)
361
+ })
362
+
363
+ it('returns null for Infinity', () => {
364
+ assert.equal(parseUsdLimit(Infinity), null)
365
+ })
366
+ })
367
+
368
+ // ---------------------------------------------------------------------------
369
+ // stripMarkupForHeartbeat
370
+ // ---------------------------------------------------------------------------
371
+ describe('stripMarkupForHeartbeat', () => {
372
+ it('strips HTML tags', () => {
373
+ assert.equal(stripMarkupForHeartbeat('<b>bold</b>'), 'bold')
374
+ })
375
+
376
+ it('strips &nbsp;', () => {
377
+ assert.equal(stripMarkupForHeartbeat('hello&nbsp;world'), 'hello world')
378
+ })
379
+
380
+ it('strips leading/trailing markdown formatting chars', () => {
381
+ assert.equal(stripMarkupForHeartbeat('**bold text**'), 'bold text')
382
+ assert.equal(stripMarkupForHeartbeat('`code`'), 'code')
383
+ assert.equal(stripMarkupForHeartbeat('~~strikethrough~~'), 'strikethrough')
384
+ })
385
+
386
+ it('trims whitespace', () => {
387
+ assert.equal(stripMarkupForHeartbeat(' hello '), 'hello')
388
+ })
389
+
390
+ it('returns empty for all-markup input', () => {
391
+ assert.equal(stripMarkupForHeartbeat('<br>'), '')
392
+ })
393
+ })
394
+
395
+ // ---------------------------------------------------------------------------
396
+ // classifyHeartbeatResponse
397
+ // ---------------------------------------------------------------------------
398
+ describe('classifyHeartbeatResponse', () => {
399
+ it('suppresses exact HEARTBEAT_OK', () => {
400
+ assert.equal(classifyHeartbeatResponse('HEARTBEAT_OK', 300, false), 'suppress')
401
+ })
402
+
403
+ it('suppresses exact NO_MESSAGE', () => {
404
+ assert.equal(classifyHeartbeatResponse('NO_MESSAGE', 300, false), 'suppress')
405
+ })
406
+
407
+ it('suppresses HEARTBEAT_OK with trailing punctuation', () => {
408
+ assert.equal(classifyHeartbeatResponse('HEARTBEAT_OK.', 300, false), 'suppress')
409
+ })
410
+
411
+ it('suppresses when only HEARTBEAT_OK remains after stripping', () => {
412
+ assert.equal(classifyHeartbeatResponse('**HEARTBEAT_OK**', 300, false), 'suppress')
413
+ })
414
+
415
+ it('suppresses short text without tool calls under ackMaxChars', () => {
416
+ assert.equal(classifyHeartbeatResponse('All systems nominal.', 300, false), 'suppress')
417
+ })
418
+
419
+ it('keeps text with tool calls even when short', () => {
420
+ assert.equal(classifyHeartbeatResponse('Ran monitoring checks.', 300, true), 'keep')
421
+ })
422
+
423
+ it('suppresses when text ends with HEARTBEAT_OK even with prefix', () => {
424
+ // The regex detects trailing HEARTBEAT_OK and suppresses the whole response
425
+ assert.equal(classifyHeartbeatResponse('Everything looks fine. HEARTBEAT_OK', 10, true), 'suppress')
426
+ })
427
+
428
+ it('strips when HEARTBEAT_OK is mid-text with real content after it', () => {
429
+ // HEARTBEAT_OK in middle, real content follows -> strip (removes control token)
430
+ const text = 'HEARTBEAT_OK — Alert: disk at 95% on node-5. Immediate attention required for production stability.'
431
+ assert.equal(classifyHeartbeatResponse(text, 50, false), 'strip')
432
+ })
433
+
434
+ it('keeps text when long and no control tokens', () => {
435
+ const longText = 'Alert: CPU usage at 95% on node-3. Memory usage approaching threshold. Immediate attention needed for the production cluster.'
436
+ assert.equal(classifyHeartbeatResponse(longText, 50, false), 'keep')
437
+ })
438
+ })
439
+
440
+ // ---------------------------------------------------------------------------
441
+ // estimateConversationTone
442
+ // ---------------------------------------------------------------------------
443
+ describe('estimateConversationTone', () => {
444
+ it('detects technical tone from code', () => {
445
+ assert.equal(estimateConversationTone('Here is the function:\n```\nconst x = 1;\n```'), 'technical')
446
+ })
447
+
448
+ it('detects technical tone from programming keywords', () => {
449
+ assert.equal(estimateConversationTone('The async function returns a Promise'), 'technical')
450
+ })
451
+
452
+ it('detects technical tone from error keywords', () => {
453
+ assert.equal(estimateConversationTone('There was a TypeError in the stack trace'), 'technical')
454
+ })
455
+
456
+ it('detects empathetic tone', () => {
457
+ assert.equal(estimateConversationTone('I understand how difficult this must be for you'), 'empathetic')
458
+ })
459
+
460
+ it('detects formal tone', () => {
461
+ assert.equal(estimateConversationTone('Furthermore, the committee has decided accordingly'), 'formal')
462
+ })
463
+
464
+ it('detects casual tone', () => {
465
+ assert.equal(estimateConversationTone('Hey, gonna grab some lunch, lol'), 'casual')
466
+ })
467
+
468
+ it('detects casual tone from multiple exclamation marks', () => {
469
+ assert.equal(estimateConversationTone('That is so great!!'), 'casual')
470
+ })
471
+
472
+ it('returns neutral for generic text', () => {
473
+ assert.equal(estimateConversationTone('The weather today is sunny with a high of 72.'), 'neutral')
474
+ })
475
+
476
+ it('returns neutral for empty string', () => {
477
+ assert.equal(estimateConversationTone(''), 'neutral')
478
+ })
479
+ })