@cloudcli-ai/cloudcli 1.37.1 → 1.37.3

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 (402) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/abnfDiagram-N423BO3Z-CJntskwA.js +1 -0
  3. package/dist/assets/arc-CIfH4uMN.js +1 -0
  4. package/dist/assets/architectureDiagram-T3A2C74G-_IpEkukg.js +36 -0
  5. package/dist/assets/blockDiagram-VBNYF7ZC-BttxrpgZ.js +132 -0
  6. package/dist/assets/c4Diagram-5PPSVZJV-g51dSRr1.js +10 -0
  7. package/dist/assets/channel-C5xU9jMw.js +1 -0
  8. package/dist/assets/chunk-2GRJ4B5K-D_-LbQUa.js +1 -0
  9. package/dist/assets/chunk-2Q5K7J3B-BtlHzwaO.js +1 -0
  10. package/dist/assets/chunk-5RXB4S5H-UzkpvpAT.js +231 -0
  11. package/dist/assets/chunk-5VM5RSS4-BKwx5Gag.js +15 -0
  12. package/dist/assets/chunk-6Q2QTUOP-D_KEBH5f.js +88 -0
  13. package/dist/assets/chunk-GF5L2VYU-rK2DGyto.js +206 -0
  14. package/dist/assets/chunk-JWPE2WC7-CeBl_N9i.js +1 -0
  15. package/dist/assets/chunk-KBJHAD2P-BiQLWVIr.js +1 -0
  16. package/dist/assets/chunk-RYQCIY6F-C1knVGV5.js +1 -0
  17. package/dist/assets/chunk-XXDRQBXY-CtKbJkvF.js +1 -0
  18. package/dist/assets/classDiagram-JCYQIIEL-CRrL3vp7.js +1 -0
  19. package/dist/assets/classDiagram-v2-OCEON4UE-CRrL3vp7.js +1 -0
  20. package/dist/assets/cose-bilkent-JH36ORCC-DjX8y40Z.js +1 -0
  21. package/dist/assets/cynefin-VYW2F7L2-DM6YeRba.js +178 -0
  22. package/dist/assets/cynefinDiagram-MW4NZA55-wZJ-vy5B.js +62 -0
  23. package/dist/assets/cytoscape.esm-C10L9Icf.js +331 -0
  24. package/dist/assets/dagre-VZM6K2ZE-BzP0ejkH.js +4 -0
  25. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  26. package/dist/assets/diagram-7IWD3JNH-BTL_j23J.js +30 -0
  27. package/dist/assets/diagram-B4RE2ZJO-Cf1Norgu.js +3 -0
  28. package/dist/assets/diagram-LBJQPF4R-DPiUEKr-.js +24 -0
  29. package/dist/assets/diagram-Q27KOJAE-CTcTpiz5.js +24 -0
  30. package/dist/assets/diagram-UB23O5K3-DPMSdYm0.js +41 -0
  31. package/dist/assets/ebnfDiagram-BXEA7PRR-7yvD6uiF.js +1 -0
  32. package/dist/assets/erDiagram-JOGREHBK-CfgeDoV6.js +85 -0
  33. package/dist/assets/flowDiagram-UKHOOZJN-B27ng5zu.js +156 -0
  34. package/dist/assets/ganttDiagram-PKOTCBZU-CcC6b_7t.js +292 -0
  35. package/dist/assets/gitGraphDiagram-DS77QQ5N-BX5kPGrO.js +106 -0
  36. package/dist/assets/graph-Cx-EqisW.js +1 -0
  37. package/dist/assets/index-BTDHQ6Id.css +32 -0
  38. package/dist/assets/index-sFfz8iBR.js +4931 -0
  39. package/dist/assets/infoDiagram-6WML65LV-CUP-SlN4.js +2 -0
  40. package/dist/assets/init-Gi6I4Gst.js +1 -0
  41. package/dist/assets/ishikawaDiagram-WSZJBQD7-BTTDhOpX.js +70 -0
  42. package/dist/assets/journeyDiagram-NVQOT4AX-ARXcc61m.js +139 -0
  43. package/dist/assets/kanban-definition-27J2QSJJ-BieMpqMA.js +89 -0
  44. package/dist/assets/layout-D6BstSzF.js +1 -0
  45. package/dist/assets/linear-61vxjRnP.js +1 -0
  46. package/dist/assets/map-CV9uif0g.js +1 -0
  47. package/dist/assets/mermaid.core-nhnwkfQx.js +312 -0
  48. package/dist/assets/mindmap-definition-FAOFIHXS-Cm6x5vT1.js +96 -0
  49. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  50. package/dist/assets/pegDiagram-VL7TDLO6-CcYBq_9H.js +1 -0
  51. package/dist/assets/pieDiagram-7S7Q4E2Y-CfvIjfzo.js +39 -0
  52. package/dist/assets/quadrantDiagram-CIZ2JOQS-SgYoWPNb.js +7 -0
  53. package/dist/assets/railroadDiagram-AXF67PYL-D2Mu5E_f.js +1 -0
  54. package/dist/assets/requirementDiagram-LRYGKXZP-Azwe_k4Q.js +84 -0
  55. package/dist/assets/sankeyDiagram-W5VNT64P-DN1Ju3PA.js +40 -0
  56. package/dist/assets/sequenceDiagram-SI44F4Z6-Bj4qC34S.js +162 -0
  57. package/dist/assets/server.browser-CtX3Hdl5.js +27 -0
  58. package/dist/assets/sizeCapture-X5ZJPWSS-Co4PdklG.js +1 -0
  59. package/dist/assets/stateDiagram-OKZ733FA-nOvL9dRC.js +1 -0
  60. package/dist/assets/stateDiagram-v2-UEYNNEHI-DNv8ppw7.js +1 -0
  61. package/dist/assets/swimlanes-SLNWSIFB-CCK3wMid.js +2 -0
  62. package/dist/assets/swimlanesDiagram-ULZ7WXOC-DnfbNQEp.js +8 -0
  63. package/dist/assets/timeline-definition-Z64GVDOM-BC29RNFm.js +120 -0
  64. package/dist/assets/{vendor-codemirror-OwyKSvPE.js → vendor-codemirror-CYrSt7-U.js} +18 -18
  65. package/dist/assets/vennDiagram-T6HMQDX7-pZlZCtEo.js +34 -0
  66. package/dist/assets/wardleyDiagram-T6FBY63Y-De8k2-zM.js +78 -0
  67. package/dist/assets/xychartDiagram-ELKLHX3M-Bk8ykH0w.js +7 -0
  68. package/dist/index.html +3 -3
  69. package/dist-server/server/index.js +8 -5
  70. package/dist-server/server/index.js.map +1 -1
  71. package/dist-server/server/modules/agent/agent.module.js +2 -1
  72. package/dist-server/server/modules/agent/agent.module.js.map +1 -1
  73. package/dist-server/server/modules/agent/agent.routes.js +2 -2
  74. package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
  75. package/dist-server/server/modules/agent/tests/agent.routes.test.js +30 -1
  76. package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -1
  77. package/dist-server/server/modules/auth/auth.middleware.js +1 -1
  78. package/dist-server/server/modules/auth/auth.middleware.js.map +1 -1
  79. package/dist-server/server/modules/browser-use/browser-use-runtime.js +10 -0
  80. package/dist-server/server/modules/browser-use/browser-use-runtime.js.map +1 -0
  81. package/dist-server/server/modules/browser-use/browser-use.routes.js +39 -33
  82. package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -1
  83. package/dist-server/server/modules/browser-use/browser-use.service.js +3 -6
  84. package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
  85. package/dist-server/server/modules/browser-use/index.js +2 -0
  86. package/dist-server/server/modules/browser-use/index.js.map +1 -1
  87. package/dist-server/server/modules/cli/cli.js +9 -3
  88. package/dist-server/server/modules/cli/cli.js.map +1 -1
  89. package/dist-server/server/modules/cli/cli.module.js +0 -1
  90. package/dist-server/server/modules/cli/cli.module.js.map +1 -1
  91. package/dist-server/server/modules/cli/cli.service.js +0 -18
  92. package/dist-server/server/modules/cli/cli.service.js.map +1 -1
  93. package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +154 -0
  94. package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js.map +1 -0
  95. package/dist-server/server/modules/cli/tests/cli.service.test.js +0 -3
  96. package/dist-server/server/modules/cli/tests/cli.service.test.js.map +1 -1
  97. package/dist-server/server/modules/database/connection.js +1 -1
  98. package/dist-server/server/modules/database/connection.js.map +1 -1
  99. package/dist-server/server/modules/database/index.js +5 -0
  100. package/dist-server/server/modules/database/index.js.map +1 -1
  101. package/dist-server/server/modules/database/migrations.js +32 -1
  102. package/dist-server/server/modules/database/migrations.js.map +1 -1
  103. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js +71 -0
  104. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js.map +1 -0
  105. package/dist-server/server/modules/database/repositories/session-drafts.db.js +105 -0
  106. package/dist-server/server/modules/database/repositories/session-drafts.db.js.map +1 -0
  107. package/dist-server/server/modules/database/repositories/sessions.db.js +137 -5
  108. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
  109. package/dist-server/server/modules/database/repositories/user-preferences.db.js +65 -0
  110. package/dist-server/server/modules/database/repositories/user-preferences.db.js.map +1 -0
  111. package/dist-server/server/modules/database/schema.js +92 -0
  112. package/dist-server/server/modules/database/schema.js.map +1 -1
  113. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +39 -0
  114. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
  115. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js +105 -0
  116. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js.map +1 -0
  117. package/dist-server/server/modules/file-tree/file-tree.service.js +18 -10
  118. package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -1
  119. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +62 -11
  120. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -1
  121. package/dist-server/server/modules/git/git-branch.service.js +15 -0
  122. package/dist-server/server/modules/git/git-branch.service.js.map +1 -0
  123. package/dist-server/server/modules/git/git.routes.js +15 -7
  124. package/dist-server/server/modules/git/git.routes.js.map +1 -1
  125. package/dist-server/server/modules/git/tests/git-branch.service.test.js +52 -0
  126. package/dist-server/server/modules/git/tests/git-branch.service.test.js.map +1 -0
  127. package/dist-server/server/modules/git/tests/git-init.routes.test.js +84 -0
  128. package/dist-server/server/modules/git/tests/git-init.routes.test.js.map +1 -1
  129. package/dist-server/server/modules/notifications/index.js +3 -1
  130. package/dist-server/server/modules/notifications/index.js.map +1 -1
  131. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +22 -1
  132. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
  133. package/dist-server/server/modules/projects/services/project-delete.service.js +12 -1
  134. package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -1
  135. package/dist-server/server/modules/projects/services/project-star.service.js +2 -2
  136. package/dist-server/server/modules/projects/services/project-star.service.js.map +1 -1
  137. package/dist-server/server/modules/providers/index.js +3 -0
  138. package/dist-server/server/modules/providers/index.js.map +1 -1
  139. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +20 -1
  140. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
  141. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js +46 -0
  142. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js.map +1 -0
  143. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +35 -6
  144. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
  145. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +422 -86
  146. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
  147. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +388 -50
  148. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  149. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +4 -1
  150. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
  151. package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
  152. package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
  153. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +204 -0
  154. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -0
  155. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js +32 -0
  156. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js.map +1 -0
  157. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +16 -0
  158. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
  159. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js +60 -49
  160. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -1
  161. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +8 -0
  162. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  163. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +1565 -432
  164. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  165. package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
  166. package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
  167. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js +8 -6
  168. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -1
  169. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
  170. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +258 -6
  171. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
  172. package/dist-server/server/modules/providers/provider.routes.js +29 -0
  173. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  174. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +14 -0
  175. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
  176. package/dist-server/server/modules/providers/services/provider-token-usage.service.js +139 -35
  177. package/dist-server/server/modules/providers/services/provider-token-usage.service.js.map +1 -1
  178. package/dist-server/server/modules/providers/services/session-conversations-search.service.js +63 -7
  179. package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
  180. package/dist-server/server/modules/providers/services/session-history-cache.service.js +87 -0
  181. package/dist-server/server/modules/providers/services/session-history-cache.service.js.map +1 -0
  182. package/dist-server/server/modules/providers/services/session-synchronizer.service.js +105 -32
  183. package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
  184. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +3 -60
  185. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  186. package/dist-server/server/modules/providers/services/sessions.service.js +179 -9
  187. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
  188. package/dist-server/server/modules/providers/tests/claude-auth.test.js +52 -0
  189. package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -1
  190. package/dist-server/server/modules/providers/tests/claude-models.test.js +39 -0
  191. package/dist-server/server/modules/providers/tests/claude-models.test.js.map +1 -0
  192. package/dist-server/server/modules/providers/tests/claude-sessions.test.js +465 -39
  193. package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -1
  194. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js +16 -0
  195. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js.map +1 -0
  196. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js +85 -0
  197. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js.map +1 -0
  198. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js +338 -0
  199. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js.map +1 -0
  200. package/dist-server/server/modules/providers/tests/codex-runtime.test.js +51 -0
  201. package/dist-server/server/modules/providers/tests/codex-runtime.test.js.map +1 -0
  202. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js +54 -0
  203. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js.map +1 -0
  204. package/dist-server/server/modules/providers/tests/codex-sessions.test.js +304 -15
  205. package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
  206. package/dist-server/server/modules/providers/tests/opencode-models.test.js +117 -4
  207. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
  208. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +164 -1
  209. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -1
  210. package/dist-server/server/modules/providers/tests/provider.routes.test.js +35 -0
  211. package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -1
  212. package/dist-server/server/modules/providers/tests/session-fork.test.js +113 -0
  213. package/dist-server/server/modules/providers/tests/session-fork.test.js.map +1 -0
  214. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js +120 -0
  215. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js.map +1 -0
  216. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js +90 -0
  217. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js.map +1 -0
  218. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js +122 -0
  219. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js.map +1 -0
  220. package/dist-server/server/modules/providers/tests/sessions.service.test.js +45 -1
  221. package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -1
  222. package/dist-server/server/modules/providers/tests/skills.test.js +15 -3
  223. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
  224. package/dist-server/server/modules/scheduled-messages/index.js +5 -0
  225. package/dist-server/server/modules/scheduled-messages/index.js.map +1 -0
  226. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js +47 -0
  227. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js.map +1 -0
  228. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js +163 -0
  229. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js.map +1 -0
  230. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js +95 -0
  231. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js.map +1 -0
  232. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js +255 -0
  233. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js.map +1 -0
  234. package/dist-server/server/modules/taskmaster/taskmaster.routes.js +23 -23
  235. package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -1
  236. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js +48 -0
  237. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js.map +1 -1
  238. package/dist-server/server/modules/user/tests/user.service.test.js +70 -0
  239. package/dist-server/server/modules/user/tests/user.service.test.js.map +1 -1
  240. package/dist-server/server/modules/user/user.module.js +10 -1
  241. package/dist-server/server/modules/user/user.module.js.map +1 -1
  242. package/dist-server/server/modules/user/user.routes.js +44 -0
  243. package/dist-server/server/modules/user/user.routes.js.map +1 -1
  244. package/dist-server/server/modules/user/user.service.js +75 -0
  245. package/dist-server/server/modules/user/user.service.js.map +1 -1
  246. package/dist-server/server/modules/voice/tests/voice.service.test.js +3 -2
  247. package/dist-server/server/modules/voice/tests/voice.service.test.js.map +1 -1
  248. package/dist-server/server/modules/websocket/index.js +7 -0
  249. package/dist-server/server/modules/websocket/index.js.map +1 -1
  250. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +10 -44
  251. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
  252. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +35 -5
  253. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -1
  254. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +166 -9
  255. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  256. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js +94 -0
  257. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js.map +1 -0
  258. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +9 -3
  259. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  260. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js +263 -0
  261. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js.map +1 -0
  262. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +29 -3
  263. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -1
  264. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js +106 -0
  265. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js.map +1 -0
  266. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +80 -0
  267. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -1
  268. package/dist-server/server/shared/claude-cli-path.js +22 -5
  269. package/dist-server/server/shared/claude-cli-path.js.map +1 -1
  270. package/dist-server/server/shared/index.js +3 -0
  271. package/dist-server/server/shared/index.js.map +1 -0
  272. package/dist-server/server/shared/message-unification.js +376 -0
  273. package/dist-server/server/shared/message-unification.js.map +1 -0
  274. package/dist-server/server/shared/tests/claude-cli-path.test.js +45 -1
  275. package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -1
  276. package/dist-server/server/shared/tests/message-unification.test.js +174 -0
  277. package/dist-server/server/shared/tests/message-unification.test.js.map +1 -0
  278. package/dist-server/server/shared/utils.js +42 -0
  279. package/dist-server/server/shared/utils.js.map +1 -1
  280. package/package.json +23 -16
  281. package/scripts/promote-dist-server.mjs +50 -0
  282. package/server/index.ts +12 -6
  283. package/server/modules/agent/agent.module.ts +2 -1
  284. package/server/modules/agent/agent.routes.ts +2 -2
  285. package/server/modules/agent/tests/agent.routes.test.ts +33 -1
  286. package/server/modules/auth/auth.middleware.ts +2 -2
  287. package/server/modules/browser-use/browser-use-runtime.ts +10 -0
  288. package/server/modules/browser-use/browser-use.routes.ts +96 -33
  289. package/server/modules/browser-use/browser-use.service.ts +5 -8
  290. package/server/modules/browser-use/index.ts +3 -0
  291. package/server/modules/cli/cli.module.ts +0 -1
  292. package/server/modules/cli/cli.service.ts +0 -20
  293. package/server/modules/cli/cli.ts +9 -3
  294. package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -0
  295. package/server/modules/cli/tests/cli.service.test.ts +0 -3
  296. package/server/modules/database/connection.ts +2 -1
  297. package/server/modules/database/index.ts +10 -0
  298. package/server/modules/database/migrations.ts +37 -0
  299. package/server/modules/database/repositories/scheduled-messages.db.ts +127 -0
  300. package/server/modules/database/repositories/session-drafts.db.ts +171 -0
  301. package/server/modules/database/repositories/sessions.db.ts +196 -3
  302. package/server/modules/database/repositories/user-preferences.db.ts +80 -0
  303. package/server/modules/database/schema.ts +96 -0
  304. package/server/modules/database/tests/sessions.db.integration.test.ts +52 -0
  305. package/server/modules/database/tests/user-preferences.db.integration.test.ts +135 -0
  306. package/server/modules/file-tree/file-tree.service.ts +21 -10
  307. package/server/modules/file-tree/tests/file-tree.service.test.ts +65 -11
  308. package/server/modules/git/git-branch.service.ts +43 -0
  309. package/server/modules/git/git.routes.ts +16 -9
  310. package/server/modules/git/tests/git-branch.service.test.ts +60 -0
  311. package/server/modules/git/tests/git-init.routes.test.ts +83 -0
  312. package/server/modules/notifications/index.ts +2 -0
  313. package/server/modules/notifications/services/notification-orchestrator.service.js +24 -1
  314. package/server/modules/projects/services/project-delete.service.ts +13 -1
  315. package/server/modules/projects/services/project-star.service.ts +2 -2
  316. package/server/modules/providers/index.ts +4 -0
  317. package/server/modules/providers/list/claude/claude-auth.provider.ts +21 -1
  318. package/server/modules/providers/list/claude/claude-fork.provider.ts +57 -0
  319. package/server/modules/providers/list/claude/claude-models.provider.ts +38 -6
  320. package/server/modules/providers/list/claude/claude-runtime.provider.js +463 -93
  321. package/server/modules/providers/list/claude/claude-sessions.provider.ts +478 -54
  322. package/server/modules/providers/list/claude/claude-skills.provider.ts +4 -1
  323. package/server/modules/providers/list/claude/claude.provider.ts +3 -0
  324. package/server/modules/providers/list/codex/codex-app-server.client.ts +243 -0
  325. package/server/modules/providers/list/codex/codex-fork.provider.ts +40 -0
  326. package/server/modules/providers/list/codex/codex-models.provider.ts +16 -0
  327. package/server/modules/providers/list/codex/{codex-runtime.provider.js → codex-runtime.provider.ts} +93 -66
  328. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +9 -0
  329. package/server/modules/providers/list/codex/codex-sessions.provider.ts +1806 -477
  330. package/server/modules/providers/list/codex/codex.provider.ts +3 -0
  331. package/server/modules/providers/list/cursor/cursor-runtime.provider.js +8 -6
  332. package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +0 -1
  333. package/server/modules/providers/list/opencode/opencode-models.provider.ts +279 -6
  334. package/server/modules/providers/provider.routes.ts +36 -0
  335. package/server/modules/providers/services/provider-capabilities.service.ts +23 -0
  336. package/server/modules/providers/services/provider-token-usage.service.ts +166 -39
  337. package/server/modules/providers/services/session-conversations-search.service.ts +94 -7
  338. package/server/modules/providers/services/session-history-cache.service.ts +139 -0
  339. package/server/modules/providers/services/session-synchronizer.service.ts +125 -38
  340. package/server/modules/providers/services/sessions-watcher.service.ts +3 -64
  341. package/server/modules/providers/services/sessions.service.ts +201 -9
  342. package/server/modules/providers/tests/claude-auth.test.ts +58 -0
  343. package/server/modules/providers/tests/claude-models.test.ts +87 -0
  344. package/server/modules/providers/tests/claude-sessions.test.ts +552 -40
  345. package/server/modules/providers/tests/claude-subagent-echo.test.ts +25 -0
  346. package/server/modules/providers/tests/claude-token-budget.test.ts +104 -0
  347. package/server/modules/providers/tests/codex-message-editing.test.ts +419 -0
  348. package/server/modules/providers/tests/codex-runtime.test.ts +58 -0
  349. package/server/modules/providers/tests/codex-session-fork.test.ts +59 -0
  350. package/server/modules/providers/tests/codex-sessions.test.ts +337 -15
  351. package/server/modules/providers/tests/opencode-models.test.ts +166 -8
  352. package/server/modules/providers/tests/provider-token-usage.service.test.ts +188 -1
  353. package/server/modules/providers/tests/provider.routes.test.ts +61 -0
  354. package/server/modules/providers/tests/session-fork.test.ts +173 -0
  355. package/server/modules/providers/tests/session-history-cache.service.test.ts +143 -0
  356. package/server/modules/providers/tests/session-orphan-prune.test.ts +107 -0
  357. package/server/modules/providers/tests/session-synchronizer-coalescing.test.ts +136 -0
  358. package/server/modules/providers/tests/sessions.service.test.ts +67 -1
  359. package/server/modules/providers/tests/skills.test.ts +21 -5
  360. package/server/modules/scheduled-messages/index.ts +8 -0
  361. package/server/modules/scheduled-messages/scheduled-messages.routes.ts +68 -0
  362. package/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts +203 -0
  363. package/server/modules/scheduled-messages/services/scheduled-messages.service.ts +121 -0
  364. package/server/modules/scheduled-messages/tests/scheduled-messages.test.ts +310 -0
  365. package/server/modules/taskmaster/taskmaster.routes.ts +23 -33
  366. package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +57 -0
  367. package/server/modules/user/tests/user.service.test.ts +90 -0
  368. package/server/modules/user/user.module.ts +10 -1
  369. package/server/modules/user/user.routes.ts +44 -0
  370. package/server/modules/user/user.service.ts +105 -0
  371. package/server/modules/voice/tests/voice.service.test.ts +3 -2
  372. package/server/modules/websocket/index.ts +8 -0
  373. package/server/modules/websocket/services/chat-run-registry.service.ts +17 -50
  374. package/server/modules/websocket/services/chat-session-writer.service.ts +36 -6
  375. package/server/modules/websocket/services/chat-websocket.service.ts +244 -15
  376. package/server/modules/websocket/services/session-upsert-broadcast.service.ts +108 -0
  377. package/server/modules/websocket/services/shell-websocket.service.ts +10 -3
  378. package/server/modules/websocket/tests/chat-edit-send.test.ts +309 -0
  379. package/server/modules/websocket/tests/chat-run-registry.test.ts +34 -3
  380. package/server/modules/websocket/tests/session-upsert-broadcast.test.ts +138 -0
  381. package/server/modules/websocket/tests/shell-websocket.service.test.ts +108 -0
  382. package/server/shared/claude-cli-path.ts +24 -6
  383. package/server/shared/index.ts +3 -0
  384. package/server/shared/interfaces.ts +62 -0
  385. package/server/shared/message-unification.ts +449 -0
  386. package/server/shared/tests/claude-cli-path.test.ts +58 -1
  387. package/server/shared/tests/message-unification.test.ts +218 -0
  388. package/server/shared/types.ts +123 -3
  389. package/server/shared/utils.ts +50 -0
  390. package/server/tsconfig.json +6 -2
  391. package/README.de.md +0 -257
  392. package/README.ja.md +0 -249
  393. package/README.ko.md +0 -249
  394. package/README.ru.md +0 -257
  395. package/README.tr.md +0 -258
  396. package/README.zh-CN.md +0 -249
  397. package/README.zh-TW.md +0 -249
  398. package/dist/assets/index-D3jfzuKN.css +0 -32
  399. package/dist/assets/index-DTMWjJj8.js +0 -1550
  400. package/dist-server/server/constants/config.js +0 -6
  401. package/dist-server/server/constants/config.js.map +0 -1
  402. package/server/constants/config.ts +0 -5
@@ -0,0 +1,121 @@
1
+ import { scheduledMessagesDb, sessionsDb } from '@/modules/database/index.js';
2
+ import type { ScheduledMessageRow } from '@/modules/database/index.js';
3
+ import { AppError } from '@/shared/utils.js';
4
+
5
+ /** How far ahead a message may be scheduled. Beyond this it is almost certainly a mistake. */
6
+ const MAX_SCHEDULE_AHEAD_MS = 365 * 24 * 60 * 60 * 1000;
7
+ const MAX_CONTENT_LENGTH = 100_000;
8
+
9
+ export type ScheduledMessage = {
10
+ id: string;
11
+ sessionId: string;
12
+ content: string;
13
+ options: Record<string, unknown>;
14
+ scheduledFor: string;
15
+ status: ScheduledMessageRow['status'];
16
+ failureReason: string | null;
17
+ createdAt: string;
18
+ };
19
+
20
+ function readOptions(raw: string): Record<string, unknown> {
21
+ try {
22
+ const parsed: unknown = JSON.parse(raw);
23
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
24
+ ? parsed as Record<string, unknown>
25
+ : {};
26
+ } catch {
27
+ // A corrupt options blob must not stop the message from being sent.
28
+ return {};
29
+ }
30
+ }
31
+
32
+ export function toScheduledMessage(row: ScheduledMessageRow): ScheduledMessage {
33
+ return {
34
+ id: row.id,
35
+ sessionId: row.session_id,
36
+ content: row.content,
37
+ options: readOptions(row.options),
38
+ scheduledFor: row.scheduled_for,
39
+ status: row.status,
40
+ failureReason: row.failure_reason,
41
+ createdAt: row.created_at,
42
+ };
43
+ }
44
+
45
+ export const scheduledMessagesService = {
46
+ /**
47
+ * Records a message to send to a session later.
48
+ *
49
+ * The time is taken as an absolute instant, so the schedule does not move if
50
+ * the user changes time zone — or schedules from a phone and is on a laptop
51
+ * when it fires.
52
+ */
53
+ schedule(input: {
54
+ userId: number;
55
+ sessionId: string;
56
+ content: string;
57
+ options?: unknown;
58
+ scheduledFor: string;
59
+ }): ScheduledMessage {
60
+ const content = input.content.trim();
61
+ if (!content) {
62
+ throw new AppError('A scheduled message needs some text.', {
63
+ code: 'CONTENT_REQUIRED',
64
+ statusCode: 400,
65
+ });
66
+ }
67
+ if (content.length > MAX_CONTENT_LENGTH) {
68
+ throw new AppError('That message is too long to schedule.', {
69
+ code: 'CONTENT_TOO_LONG',
70
+ statusCode: 400,
71
+ });
72
+ }
73
+
74
+ const scheduledFor = new Date(input.scheduledFor);
75
+ if (Number.isNaN(scheduledFor.getTime())) {
76
+ throw new AppError('scheduledFor must be an ISO timestamp.', {
77
+ code: 'INVALID_SCHEDULE_TIME',
78
+ statusCode: 400,
79
+ });
80
+ }
81
+ if (scheduledFor.getTime() - Date.now() > MAX_SCHEDULE_AHEAD_MS) {
82
+ throw new AppError('That is too far in the future to schedule.', {
83
+ code: 'SCHEDULE_TOO_FAR_AHEAD',
84
+ statusCode: 400,
85
+ });
86
+ }
87
+
88
+ if (!sessionsDb.getSessionById(input.sessionId)) {
89
+ throw new AppError(`Session "${input.sessionId}" was not found.`, {
90
+ code: 'SESSION_NOT_FOUND',
91
+ statusCode: 404,
92
+ });
93
+ }
94
+
95
+ return toScheduledMessage(scheduledMessagesDb.create({
96
+ userId: input.userId,
97
+ sessionId: input.sessionId,
98
+ content,
99
+ options: input.options ?? {},
100
+ scheduledFor,
101
+ }));
102
+ },
103
+
104
+ listForSession(userId: number, sessionId: string): ScheduledMessage[] {
105
+ return scheduledMessagesDb.listForSession(userId, sessionId).map(toScheduledMessage);
106
+ },
107
+
108
+ listPending(userId: number): ScheduledMessage[] {
109
+ return scheduledMessagesDb.listPendingForUser(userId).map(toScheduledMessage);
110
+ },
111
+
112
+ /** Cancels a pending message, or dismisses a failed one. */
113
+ cancel(userId: number, id: string): void {
114
+ if (!scheduledMessagesDb.cancel(userId, id)) {
115
+ throw new AppError('That message has already been sent or cancelled.', {
116
+ code: 'SCHEDULED_MESSAGE_NOT_PENDING',
117
+ statusCode: 409,
118
+ });
119
+ }
120
+ },
121
+ };
@@ -0,0 +1,310 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdtemp, rm } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import path from 'node:path';
5
+ import test from 'node:test';
6
+
7
+ import { closeConnection, initializeDatabase, scheduledMessagesDb, sessionDraftsDb, sessionsDb, userDb } from '@/modules/database/index.js';
8
+ import { dispatchDueScheduledMessages, dispatchQueuedMessages } from '@/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js';
9
+ import { scheduledMessagesService } from '@/modules/scheduled-messages/services/scheduled-messages.service.js';
10
+ import { chatRunRegistry } from '@/modules/websocket/index.js';
11
+
12
+ const SESSION_ID = 'scheduled-session';
13
+
14
+ async function withIsolatedDatabase(runTest: (userId: number) => void | Promise<void>): Promise<void> {
15
+ const previousDatabasePath = process.env.DATABASE_PATH;
16
+ const tempDirectory = await mkdtemp(path.join(tmpdir(), 'scheduled-messages-'));
17
+
18
+ closeConnection();
19
+ process.env.DATABASE_PATH = path.join(tempDirectory, 'auth.db');
20
+ await initializeDatabase();
21
+
22
+ try {
23
+ const user = userDb.createUser('scheduler', 'hash');
24
+ sessionsDb.createAppSession(SESSION_ID, 'claude', tempDirectory, 'Scheduled session');
25
+ await runTest(Number(user.id));
26
+ } finally {
27
+ chatRunRegistry.clearAll();
28
+ closeConnection();
29
+ if (previousDatabasePath === undefined) {
30
+ delete process.env.DATABASE_PATH;
31
+ } else {
32
+ process.env.DATABASE_PATH = previousDatabasePath;
33
+ }
34
+ await rm(tempDirectory, { recursive: true, force: true });
35
+ }
36
+ }
37
+
38
+ type RunCall = { provider: string; command: string; options: Record<string, unknown> };
39
+
40
+ function createRuntime(runs: RunCall[], behaviour: 'ok' | 'throw' = 'ok', aborts: string[] = []) {
41
+ return {
42
+ hasRuntime: () => true,
43
+ run: async (provider: string, command: string, options: Record<string, unknown>) => {
44
+ if (behaviour === 'throw') {
45
+ throw new Error('provider exploded');
46
+ }
47
+ runs.push({ provider, command, options });
48
+ },
49
+ abort: async (_provider: string, sessionId: string) => {
50
+ aborts.push(sessionId);
51
+ return true;
52
+ },
53
+ } as never;
54
+ }
55
+
56
+ test('a message due in the past is sent on the next pass, not skipped', async () => {
57
+ await withIsolatedDatabase(async (userId) => {
58
+ // The server was down when this came due.
59
+ scheduledMessagesService.schedule({
60
+ userId,
61
+ sessionId: SESSION_ID,
62
+ content: 'run the nightly checks',
63
+ scheduledFor: new Date(Date.now() - 60_000).toISOString(),
64
+ });
65
+
66
+ const runs: RunCall[] = [];
67
+ const sent = await dispatchDueScheduledMessages(createRuntime(runs));
68
+
69
+ assert.equal(sent, 1);
70
+ assert.equal(runs.length, 1);
71
+ assert.equal(runs[0].command, 'run the nightly checks');
72
+ assert.equal(scheduledMessagesDb.listForSession(userId, SESSION_ID)[0].status, 'sent');
73
+ });
74
+ });
75
+
76
+ test('a queued message is sent by the server without a browser connection', async () => {
77
+ await withIsolatedDatabase(async (userId) => {
78
+ sessionDraftsDb.saveDraft(userId, SESSION_ID, {
79
+ text: '',
80
+ queuedMessage: {
81
+ content: 'continue on the VPS',
82
+ options: { model: 'claude-opus-5' },
83
+ attachments: [{ path: '/tmp/upload.png' }],
84
+ },
85
+ });
86
+
87
+ const runs: RunCall[] = [];
88
+ assert.equal(await dispatchQueuedMessages(createRuntime(runs)), 1);
89
+ assert.equal(runs.length, 1);
90
+ assert.equal(runs[0].command, 'continue on the VPS');
91
+ assert.equal(runs[0].options.model, 'claude-opus-5');
92
+ assert.deepEqual(runs[0].options.attachments, []);
93
+ assert.equal(sessionDraftsDb.getDrafts(userId).length, 0);
94
+ });
95
+ });
96
+
97
+ test('a queued message stays pending while its session is busy', async () => {
98
+ await withIsolatedDatabase(async (userId) => {
99
+ sessionDraftsDb.saveDraft(userId, SESSION_ID, {
100
+ text: '',
101
+ queuedMessage: { content: 'send after this run' },
102
+ });
103
+ chatRunRegistry.startRun({
104
+ appSessionId: SESSION_ID,
105
+ provider: 'claude',
106
+ providerSessionId: null,
107
+ connection: null,
108
+ userId,
109
+ });
110
+
111
+ const runs: RunCall[] = [];
112
+ assert.equal(await dispatchQueuedMessages(createRuntime(runs)), 0);
113
+ assert.equal(runs.length, 0);
114
+ assert.deepEqual(sessionDraftsDb.getDrafts(userId)[0]?.queuedMessage, {
115
+ content: 'send after this run',
116
+ });
117
+ });
118
+ });
119
+
120
+ test('a due message interrupts a run in progress instead of failing', async () => {
121
+ await withIsolatedDatabase(async (userId) => {
122
+ scheduledMessagesService.schedule({
123
+ userId,
124
+ sessionId: SESSION_ID,
125
+ content: 'the schedule wins',
126
+ scheduledFor: new Date(Date.now() - 1_000).toISOString(),
127
+ });
128
+ chatRunRegistry.startRun({
129
+ appSessionId: SESSION_ID,
130
+ provider: 'claude',
131
+ providerSessionId: null,
132
+ connection: null,
133
+ userId,
134
+ });
135
+
136
+ const runs: RunCall[] = [];
137
+ const aborts: string[] = [];
138
+ assert.equal(await dispatchDueScheduledMessages(createRuntime(runs, 'ok', aborts)), 1);
139
+
140
+ assert.deepEqual(aborts, [SESSION_ID]);
141
+ assert.equal(runs.length, 1);
142
+ assert.equal(runs[0].command, 'the schedule wins');
143
+ assert.equal(scheduledMessagesDb.listForSession(userId, SESSION_ID)[0].status, 'sent');
144
+ });
145
+ });
146
+
147
+ test('a message that is not due yet is left alone', async () => {
148
+ await withIsolatedDatabase(async (userId) => {
149
+ scheduledMessagesService.schedule({
150
+ userId,
151
+ sessionId: SESSION_ID,
152
+ content: 'later',
153
+ scheduledFor: new Date(Date.now() + 3_600_000).toISOString(),
154
+ });
155
+
156
+ const runs: RunCall[] = [];
157
+ assert.equal(await dispatchDueScheduledMessages(createRuntime(runs)), 0);
158
+ assert.equal(runs.length, 0);
159
+ assert.equal(scheduledMessagesDb.listForSession(userId, SESSION_ID)[0].status, 'pending');
160
+ });
161
+ });
162
+
163
+ test('a due message is claimed once, so overlapping passes cannot double-send it', async () => {
164
+ await withIsolatedDatabase(async (userId) => {
165
+ scheduledMessagesService.schedule({
166
+ userId,
167
+ sessionId: SESSION_ID,
168
+ content: 'only once',
169
+ scheduledFor: new Date(Date.now() - 1_000).toISOString(),
170
+ });
171
+
172
+ const runs: RunCall[] = [];
173
+ const runtime = createRuntime(runs);
174
+ await Promise.all([
175
+ dispatchDueScheduledMessages(runtime),
176
+ dispatchDueScheduledMessages(runtime),
177
+ ]);
178
+
179
+ assert.equal(runs.length, 1);
180
+ });
181
+ });
182
+
183
+ test('the composer settings it was scheduled with travel with it', async () => {
184
+ await withIsolatedDatabase(async (userId) => {
185
+ scheduledMessagesService.schedule({
186
+ userId,
187
+ sessionId: SESSION_ID,
188
+ content: 'with options',
189
+ options: { model: 'claude-opus-5', permissionMode: 'plan' },
190
+ scheduledFor: new Date(Date.now() - 1_000).toISOString(),
191
+ });
192
+
193
+ const runs: RunCall[] = [];
194
+ await dispatchDueScheduledMessages(createRuntime(runs));
195
+
196
+ assert.equal(runs[0].options.model, 'claude-opus-5');
197
+ assert.equal(runs[0].options.permissionMode, 'plan');
198
+ });
199
+ });
200
+
201
+ test('a provider failure is recorded on the message instead of vanishing', async () => {
202
+ await withIsolatedDatabase(async (userId) => {
203
+ scheduledMessagesService.schedule({
204
+ userId,
205
+ sessionId: SESSION_ID,
206
+ content: 'will fail',
207
+ scheduledFor: new Date(Date.now() - 1_000).toISOString(),
208
+ });
209
+
210
+ await dispatchDueScheduledMessages(createRuntime([], 'throw'));
211
+
212
+ const row = scheduledMessagesDb.listForSession(userId, SESSION_ID)[0];
213
+ assert.equal(row.status, 'failed');
214
+ assert.match(row.failure_reason ?? '', /provider exploded/);
215
+ });
216
+ });
217
+
218
+ test('a cancelled message never fires', async () => {
219
+ await withIsolatedDatabase(async (userId) => {
220
+ const scheduled = scheduledMessagesService.schedule({
221
+ userId,
222
+ sessionId: SESSION_ID,
223
+ content: 'never mind',
224
+ scheduledFor: new Date(Date.now() - 1_000).toISOString(),
225
+ });
226
+ scheduledMessagesService.cancel(userId, scheduled.id);
227
+
228
+ const runs: RunCall[] = [];
229
+ assert.equal(await dispatchDueScheduledMessages(createRuntime(runs)), 0);
230
+ assert.equal(runs.length, 0);
231
+ });
232
+ });
233
+
234
+ test('a failed message can be dismissed, and stays dismissed', async () => {
235
+ await withIsolatedDatabase(async (userId) => {
236
+ const scheduled = scheduledMessagesService.schedule({
237
+ userId,
238
+ sessionId: SESSION_ID,
239
+ content: 'will fail',
240
+ scheduledFor: new Date(Date.now() - 1_000).toISOString(),
241
+ });
242
+ await dispatchDueScheduledMessages(createRuntime([], 'throw'));
243
+ assert.equal(scheduledMessagesDb.listForSession(userId, SESSION_ID)[0].status, 'failed');
244
+
245
+ scheduledMessagesService.cancel(userId, scheduled.id);
246
+
247
+ assert.equal(scheduledMessagesDb.listForSession(userId, SESSION_ID)[0].status, 'cancelled');
248
+ });
249
+ });
250
+
251
+ test('cancelling something that already fired is refused', async () => {
252
+ await withIsolatedDatabase(async (userId) => {
253
+ const scheduled = scheduledMessagesService.schedule({
254
+ userId,
255
+ sessionId: SESSION_ID,
256
+ content: 'gone',
257
+ scheduledFor: new Date(Date.now() - 1_000).toISOString(),
258
+ });
259
+ await dispatchDueScheduledMessages(createRuntime([]));
260
+
261
+ assert.throws(
262
+ () => scheduledMessagesService.cancel(userId, scheduled.id),
263
+ (error: Error & { code?: string }) => error.code === 'SCHEDULED_MESSAGE_NOT_PENDING',
264
+ );
265
+ });
266
+ });
267
+
268
+ test('one user cannot cancel another user\'s scheduled message', async () => {
269
+ await withIsolatedDatabase(async (userId) => {
270
+ const scheduled = scheduledMessagesService.schedule({
271
+ userId,
272
+ sessionId: SESSION_ID,
273
+ content: 'mine',
274
+ scheduledFor: new Date(Date.now() + 3_600_000).toISOString(),
275
+ });
276
+
277
+ assert.throws(
278
+ () => scheduledMessagesService.cancel(userId + 1, scheduled.id),
279
+ (error: Error & { code?: string }) => error.code === 'SCHEDULED_MESSAGE_NOT_PENDING',
280
+ );
281
+ assert.equal(scheduledMessagesDb.listForSession(userId, SESSION_ID)[0].status, 'pending');
282
+ });
283
+ });
284
+
285
+ test('scheduling validates its input', async () => {
286
+ await withIsolatedDatabase(async (userId) => {
287
+ const base = { userId, sessionId: SESSION_ID, scheduledFor: new Date(Date.now() + 1000).toISOString() };
288
+
289
+ assert.throws(
290
+ () => scheduledMessagesService.schedule({ ...base, content: ' ' }),
291
+ (error: Error & { code?: string }) => error.code === 'CONTENT_REQUIRED',
292
+ );
293
+ assert.throws(
294
+ () => scheduledMessagesService.schedule({ ...base, content: 'hi', scheduledFor: 'not a date' }),
295
+ (error: Error & { code?: string }) => error.code === 'INVALID_SCHEDULE_TIME',
296
+ );
297
+ assert.throws(
298
+ () => scheduledMessagesService.schedule({
299
+ ...base,
300
+ content: 'hi',
301
+ scheduledFor: new Date(Date.now() + 400 * 24 * 3600 * 1000).toISOString(),
302
+ }),
303
+ (error: Error & { code?: string }) => error.code === 'SCHEDULE_TOO_FAR_AHEAD',
304
+ );
305
+ assert.throws(
306
+ () => scheduledMessagesService.schedule({ ...base, sessionId: 'nope', content: 'hi' }),
307
+ (error: Error & { code?: string }) => error.code === 'SESSION_NOT_FOUND',
308
+ );
309
+ });
310
+ });
@@ -13,6 +13,8 @@ import path from 'path';
13
13
 
14
14
  import express from 'express';
15
15
 
16
+ import { WS_OPEN_STATE, connectedClients } from '@/modules/websocket/index.js';
17
+
16
18
  import type { createTaskmasterService } from './taskmaster.service.js';
17
19
  // cross-spawn: drop-in spawn with Windows .cmd/PATHEXT resolution — required
18
20
  // here since task-master/npx are .cmd shims on Windows.
@@ -25,22 +27,30 @@ type TaskmasterRouterDependencies = {
25
27
  taskmasterService: ReturnType<typeof createTaskmasterService>;
26
28
  };
27
29
 
28
- function broadcastTaskMasterUpdate(wss, type, projectId, payloadKey, payload) {
29
- if (!wss || !projectId) return;
30
+ /**
31
+ * These frames are `type`-keyed and only TaskMasterContext, which lives on the
32
+ * `/ws` chat socket, knows what to do with them. Broadcasting over the raw
33
+ * `wss.clients` set instead delivered them to every `/shell`, `/plugin-ws` and
34
+ * `/desktop-notifications` socket as well, where they were parsed and dropped —
35
+ * and, on `/plugin-ws`, handed to third-party plugin frontends that have no
36
+ * business seeing them. `connectedClients` is the `/ws`-only set every other
37
+ * broadcast in the server already uses.
38
+ */
39
+ function broadcastTaskMasterUpdate(type, projectId, payloadKey, payload) {
40
+ if (!projectId) return;
30
41
  const message = JSON.stringify({
31
42
  type,
32
43
  projectId,
33
44
  ...(payload === undefined ? {} : { [payloadKey]: payload }),
34
45
  timestamp: new Date().toISOString(),
35
46
  });
36
- wss.clients.forEach((client) => {
37
- if (client.readyState === 1) client.send(message);
47
+ connectedClients.forEach((client) => {
48
+ if (client.readyState === WS_OPEN_STATE) client.send(message);
38
49
  });
39
50
  }
40
51
 
41
- function broadcastTaskMasterProjectUpdate(wss, projectId, taskMasterData) {
52
+ function broadcastTaskMasterProjectUpdate(projectId, taskMasterData) {
42
53
  broadcastTaskMasterUpdate(
43
- wss,
44
54
  'taskmaster-project-updated',
45
55
  projectId,
46
56
  'taskMasterData',
@@ -48,8 +58,8 @@ function broadcastTaskMasterProjectUpdate(wss, projectId, taskMasterData) {
48
58
  );
49
59
  }
50
60
 
51
- function broadcastTaskMasterTasksUpdate(wss, projectId, tasksData) {
52
- broadcastTaskMasterUpdate(wss, 'taskmaster-tasks-updated', projectId, 'tasksData', tasksData);
61
+ function broadcastTaskMasterTasksUpdate(projectId, tasksData) {
62
+ broadcastTaskMasterUpdate('taskmaster-tasks-updated', projectId, 'tasksData', tasksData);
53
63
  }
54
64
 
55
65
  /**
@@ -596,13 +606,7 @@ export function createTaskmasterRouter(dependencies: TaskmasterRouterDependencie
596
606
  // Broadcast TaskMaster project update via WebSocket. The
597
607
  // WebSocket payload keeps using `projectId` so the frontend
598
608
  // can match notifications against the current selection.
599
- if (req.app.locals.wss) {
600
- broadcastTaskMasterProjectUpdate(
601
- req.app.locals.wss,
602
- projectId,
603
- { hasTaskmaster: true, status: 'initialized' }
604
- );
605
- }
609
+ broadcastTaskMasterProjectUpdate(projectId, { hasTaskmaster: true, status: 'initialized' });
606
610
 
607
611
  res.json({
608
612
  projectId,
@@ -688,12 +692,7 @@ export function createTaskmasterRouter(dependencies: TaskmasterRouterDependencie
688
692
  if (code === 0) {
689
693
  // Broadcast task update via WebSocket using the projectId so
690
694
  // clients subscribed to this project get notified immediately.
691
- if (req.app.locals.wss) {
692
- broadcastTaskMasterTasksUpdate(
693
- req.app.locals.wss,
694
- projectId
695
- );
696
- }
695
+ broadcastTaskMasterTasksUpdate(projectId);
697
696
 
698
697
  res.json({
699
698
  projectId,
@@ -748,9 +747,7 @@ export function createTaskmasterRouter(dependencies: TaskmasterRouterDependencie
748
747
  }, ({ code, error, stdout, stderr }) => {
749
748
  if (code === 0) {
750
749
  // Broadcast task update via WebSocket
751
- if (req.app.locals.wss) {
752
- broadcastTaskMasterTasksUpdate(req.app.locals.wss, projectId);
753
- }
750
+ broadcastTaskMasterTasksUpdate(projectId);
754
751
 
755
752
  res.json({
756
753
  projectId,
@@ -787,9 +784,7 @@ export function createTaskmasterRouter(dependencies: TaskmasterRouterDependencie
787
784
  }, ({ code, error, stdout, stderr }) => {
788
785
  if (code === 0) {
789
786
  // Broadcast task update via WebSocket
790
- if (req.app.locals.wss) {
791
- broadcastTaskMasterTasksUpdate(req.app.locals.wss, projectId);
792
- }
787
+ broadcastTaskMasterTasksUpdate(projectId);
793
788
 
794
789
  res.json({
795
790
  projectId,
@@ -870,12 +865,7 @@ export function createTaskmasterRouter(dependencies: TaskmasterRouterDependencie
870
865
  }, ({ code, error, stdout, stderr }) => {
871
866
  if (code === 0) {
872
867
  // Broadcast task update via WebSocket
873
- if (req.app.locals.wss) {
874
- broadcastTaskMasterTasksUpdate(
875
- req.app.locals.wss,
876
- projectId
877
- );
878
- }
868
+ broadcastTaskMasterTasksUpdate(projectId);
879
869
 
880
870
  res.json({
881
871
  projectId,
@@ -6,6 +6,8 @@ import test from 'node:test';
6
6
 
7
7
  import express from 'express';
8
8
 
9
+ import { connectedClients } from '@/modules/websocket/index.js';
10
+
9
11
  import { createTaskmasterRouter } from '../taskmaster.routes.js';
10
12
 
11
13
  test('tasks route resolves project ids through the injected project adapter', async () => {
@@ -142,3 +144,58 @@ test('TaskMaster process errors use the endpoint failure response and settle onc
142
144
  await new Promise<void>((resolve) => server.close(() => resolve()));
143
145
  }
144
146
  });
147
+
148
+ test('TaskMaster broadcasts reach only the tracked chat clients', async () => {
149
+ const child = new EventEmitter() as EventEmitter & {
150
+ stdin: PassThrough;
151
+ stdout: PassThrough;
152
+ stderr: PassThrough;
153
+ };
154
+ child.stdin = new PassThrough();
155
+ child.stdout = new PassThrough();
156
+ child.stderr = new PassThrough();
157
+
158
+ const chatClient = { readyState: 1, frames: [] as string[], send(data: string) { this.frames.push(data); } };
159
+ const closingClient = { readyState: 3, frames: [] as string[], send(data: string) { this.frames.push(data); } };
160
+
161
+ const router = createTaskmasterRouter({
162
+ fileSystem: { constants: { F_OK: 0 } } as typeof import('node:fs'),
163
+ fileSystemPromises: {
164
+ access: async () => { throw new Error('not initialized'); },
165
+ } as unknown as typeof import('node:fs/promises'),
166
+ spawnProcess: (() => {
167
+ process.nextTick(() => child.emit('close', 0));
168
+ return child;
169
+ }) as unknown as Parameters<typeof createTaskmasterRouter>[0]['spawnProcess'],
170
+ resolveProjectPathById: () => '/workspace/project',
171
+ taskmasterService: {
172
+ detectMcpServer: async () => ({
173
+ hasMCPServer: false,
174
+ reason: 'Not configured',
175
+ hasConfig: false,
176
+ }),
177
+ },
178
+ });
179
+ const app = express().use('/api/taskmaster', router);
180
+ const server = app.listen(0, '127.0.0.1');
181
+ await once(server, 'listening');
182
+
183
+ connectedClients.add(chatClient as never);
184
+ connectedClients.add(closingClient as never);
185
+ try {
186
+ const address = server.address() as AddressInfo;
187
+ const response = await fetch(`http://127.0.0.1:${address.port}/api/taskmaster/init/project-1`, {
188
+ method: 'POST',
189
+ });
190
+ assert.equal(response.status, 200);
191
+
192
+ assert.equal(chatClient.frames.length, 1);
193
+ const frame = JSON.parse(chatClient.frames[0]) as Record<string, unknown>;
194
+ assert.equal(frame.type, 'taskmaster-project-updated');
195
+ assert.equal(frame.projectId, 'project-1');
196
+ assert.equal(closingClient.frames.length, 0);
197
+ } finally {
198
+ connectedClients.clear();
199
+ await new Promise<void>((resolve) => server.close(() => resolve()));
200
+ }
201
+ });