openclaw-multi-auto 1.3.7 → 1.3.8

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 (503) hide show
  1. package/dist/{accounts-L9ByEpnP.js → accounts-C9HcPI9h.js} +2 -2
  2. package/dist/{accounts-BOzyfwW4.js → accounts-C_lW3Ag9.js} +2 -2
  3. package/dist/{accounts-yfBeCZtS.js → accounts-Tgelvk0C.js} +17 -17
  4. package/dist/{active-listener-D1yqT1cw.js → active-listener-BEdprTkn.js} +2 -2
  5. package/dist/{api-key-rotation-DtsNS2Nb.js → api-key-rotation-BJpKWXy0.js} +2 -2
  6. package/dist/{audio-preflight-DpxQCpsA.js → audio-preflight-BMvgEQ5j.js} +32 -32
  7. package/dist/{audio-preflight-5FEeDooz.js → audio-preflight-DDBLZBdb.js} +4 -4
  8. package/dist/{audio-transcription-runner-B-UvoDjZ.js → audio-transcription-runner-DZbSWT9E.js} +1 -1
  9. package/dist/{audio-transcription-runner-28fcRNNi.js → audio-transcription-runner-gLFfz8fr.js} +12 -12
  10. package/dist/{audit-membership-runtime-DWyHWAHM.js → audit-membership-runtime-Dntemq07.js} +4 -4
  11. package/dist/build-info.json +3 -3
  12. package/dist/bundled/boot-md/handler.js +51 -51
  13. package/dist/bundled/bootstrap-extra-files/handler.js +6 -6
  14. package/dist/bundled/command-logger/handler.js +2 -2
  15. package/dist/bundled/session-memory/handler.js +51 -51
  16. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  17. package/dist/{channel-activity-xHOMiarp.js → channel-activity-BDnjYF7B.js} +3 -3
  18. package/dist/{chrome-D45SyhQL.js → chrome-CMU2WVFh.js} +8 -8
  19. package/dist/{chrome-DwizpzOC.js → chrome-DxxEKrY7.js} +18 -18
  20. package/dist/{commands-registry-V1zZ5pPC.js → commands-registry-D5qXbFJn.js} +4 -4
  21. package/dist/{deliver-B9cys0EZ.js → deliver-BXVcFIHL.js} +1 -1
  22. package/dist/{deliver-D4o6VIur.js → deliver-DbdywYJE.js} +21 -21
  23. package/dist/deliver-runtime-BFs7iAZF.js +36 -0
  24. package/dist/{deliver-runtime-DhaQJ0pI.js → deliver-runtime-DTaIS-1i.js} +3 -3
  25. package/dist/deps-send-discord.runtime-DZUccI6Z.js +26 -0
  26. package/dist/deps-send-imessage.runtime-CF3OpoqY.js +25 -0
  27. package/dist/deps-send-signal.runtime-Cw4-ozeO.js +24 -0
  28. package/dist/deps-send-slack.runtime-BDsDhS1P.js +22 -0
  29. package/dist/deps-send-telegram.runtime-D_4xVasO.js +27 -0
  30. package/dist/{deps-send-whatsapp.runtime-DvTL2tzN.js → deps-send-whatsapp.runtime-CIZqFAqb.js} +7 -7
  31. package/dist/deps-send-whatsapp.runtime-DK8jqd14.js +60 -0
  32. package/dist/{diagnostic-Bn4PZjMZ.js → diagnostic-Co6Kghr-.js} +2 -2
  33. package/dist/{plugin-sdk/errors-CtMWwS2Z.js → errors-xt401nuk.js} +1 -1
  34. package/dist/extensionAPI.js +6 -6
  35. package/dist/{fetch-BlJWzEP6.js → fetch-DuraYswo.js} +5 -5
  36. package/dist/{fetch-guard-ChYBwfiy.js → fetch-guard-DWr0d00H.js} +2 -2
  37. package/dist/{frontmatter-CvaMP376.js → frontmatter-BkTfEZ93.js} +3 -3
  38. package/dist/{fs-safe-0jAo_Whb.js → fs-safe-CTYUrIgQ.js} +4 -4
  39. package/dist/{github-copilot-token-D13V9YBz.js → github-copilot-token-BDioPmd6.js} +7 -7
  40. package/dist/{image-DAOPwVXi.js → image-BCVLo0qw.js} +1 -1
  41. package/dist/{image-Bbn53mzj.js → image-eT7Y-nP5.js} +6 -6
  42. package/dist/{image-ops-CehkHxmW.js → image-ops-BuUnEOE0.js} +2 -2
  43. package/dist/image-runtime-BcAK3n8a.js +29 -0
  44. package/dist/{image-runtime-wlCLVvVv.js → image-runtime-DtCKpMPZ.js} +3 -3
  45. package/dist/{ir-DAP-B-Xw.js → ir-B83looB-.js} +8 -8
  46. package/dist/{legacy-names-TyzbVqa_.js → legacy-names-DOC03BkU.js} +1 -1
  47. package/dist/llm-slug-generator.js +51 -51
  48. package/dist/{logger-DMZQQtxK.js → logger-BfjWMCSD.js} +7 -7
  49. package/dist/{login-DiCctRo1.js → login-CrIwcrVI.js} +5 -5
  50. package/dist/{login-qr-MUbXgjtd.js → login-qr-BpPDZdl_.js} +10 -10
  51. package/dist/{manager-BW_NSIMl.js → manager-1bvuGrNR.js} +13 -13
  52. package/dist/manager-runtime-FO1Sx3W8.js +18 -0
  53. package/dist/{model-selection-idoqPmw0.js → model-selection-Dna0Gz1k.js} +43 -43
  54. package/dist/{outbound-C2kanETZ.js → outbound-ChDjtuD6.js} +6 -6
  55. package/dist/{outbound-attachment-DBrYWX8h.js → outbound-attachment-DqHlD21U.js} +2 -2
  56. package/dist/{path-alias-guards-DqXRZmsL.js → path-alias-guards-BzvdLvTI.js} +1 -1
  57. package/dist/{paths-CCxysrzL.js → paths-Bkr-BCxW.js} +4 -4
  58. package/dist/{paths-C6TxBCvO.js → paths-Cvc9EM8Y.js} +5 -5
  59. package/dist/{pi-embedded-BaGj07T0.js → pi-embedded-BQQa91aA.js} +158 -158
  60. package/dist/{pi-embedded-DYU79yGe.js → pi-embedded-CgQ_W6Xs.js} +24 -24
  61. package/dist/{pi-embedded-helpers-wy0DZvx1.js → pi-embedded-helpers-CLXm10bV.js} +52 -52
  62. package/dist/{pi-embedded-helpers-uTRAmQ4n.js → pi-embedded-helpers-CwuBTKza.js} +3 -3
  63. package/dist/{plugin-sdk/pi-model-discovery-v-XPUOOf.js → pi-model-discovery-Dymwdjt0.js} +2 -2
  64. package/dist/pi-model-discovery-runtime-BeY4EUPp.js +11 -0
  65. package/dist/{pi-tools.before-tool-call.runtime-BuLxSyx9.js → pi-tools.before-tool-call.runtime-Cwab_5W1.js} +9 -9
  66. package/dist/plugin-sdk/{accounts-DyFCXtHv.js → accounts-BslAlVYS.js} +2 -2
  67. package/dist/plugin-sdk/{accounts-BJAXxY46.js → accounts-C3m65--E.js} +2 -2
  68. package/dist/plugin-sdk/{accounts-C1j7HSL0.js → accounts-CNCCkdEF.js} +3 -3
  69. package/dist/plugin-sdk/{active-listener-B_sLJTXM.js → active-listener-CkPnMUkB.js} +2 -2
  70. package/dist/plugin-sdk/{api-key-rotation-8nyyt1kx.js → api-key-rotation-BXnNsojA.js} +2 -2
  71. package/dist/plugin-sdk/{audio-preflight-C_aSAPR1.js → audio-preflight-CtO4fFvp.js} +26 -26
  72. package/dist/plugin-sdk/{audio-transcription-runner-CB53F7_7.js → audio-transcription-runner-DnxvOS1-.js} +11 -11
  73. package/dist/plugin-sdk/{audit-membership-runtime-BXndI4LG.js → audit-membership-runtime-BpfoSk8M.js} +2 -2
  74. package/dist/plugin-sdk/{channel-activity-C5y8AgAV.js → channel-activity-WJYxcJ3S.js} +3 -3
  75. package/dist/plugin-sdk/{channel-web-DBTRO03V.js → channel-web-dO5k3ubM.js} +18 -18
  76. package/dist/plugin-sdk/{chrome-f00sZkDX.js → chrome-CjNTuJML.js} +6 -6
  77. package/dist/plugin-sdk/{commands-registry-BJ_NxG2F.js → commands-registry-CdYjoI0i.js} +4 -4
  78. package/dist/plugin-sdk/{common-Cf27Jwxu.js → common-oYc5vPFl.js} +2 -2
  79. package/dist/plugin-sdk/{config-CHQrpx-Q.js → config-B1z-UxQ3.js} +7 -7
  80. package/dist/plugin-sdk/{deliver-DNEuetST.js → deliver-D5_6T567.js} +10 -10
  81. package/dist/plugin-sdk/deliver-runtime-C5dgvvga.js +32 -0
  82. package/dist/plugin-sdk/deps-send-discord.runtime-Dg4N7PHJ.js +23 -0
  83. package/dist/plugin-sdk/deps-send-imessage.runtime-0OEwzMQm.js +22 -0
  84. package/dist/plugin-sdk/deps-send-signal.runtime-BM1jRt3G.js +21 -0
  85. package/dist/plugin-sdk/deps-send-slack.runtime-1E3BYRdF.js +19 -0
  86. package/dist/plugin-sdk/deps-send-telegram.runtime-DNCxIflA.js +24 -0
  87. package/dist/plugin-sdk/deps-send-whatsapp.runtime-OLwr-9c8.js +57 -0
  88. package/dist/plugin-sdk/{diagnostic-LYUUmjJ5.js → diagnostic-Bxxu0ig-.js} +2 -2
  89. package/dist/plugin-sdk/{errors-DaiAM-yU.js → errors-B3cHyZZA.js} +1 -1
  90. package/dist/plugin-sdk/{fetch-guard-CxYB5Kg6.js → fetch-guard-Dcgod0tg.js} +2 -2
  91. package/dist/plugin-sdk/{fs-safe-DtfhxbrI.js → fs-safe-BaKqI3G4.js} +3 -3
  92. package/dist/plugin-sdk/{image-BwjYjRHx.js → image-B2mQW9Rb.js} +6 -6
  93. package/dist/plugin-sdk/{image-ops-BnZKcbd6.js → image-ops-Cbzr4U9l.js} +2 -2
  94. package/dist/plugin-sdk/image-runtime-BFm45j49.js +25 -0
  95. package/dist/plugin-sdk/index.js +50 -50
  96. package/dist/plugin-sdk/{ir-Z4hX67TJ.js → ir-ZEmrTr4J.js} +7 -7
  97. package/dist/plugin-sdk/{local-roots-KhjQw04O.js → local-roots-CIPRxA-4.js} +4 -4
  98. package/dist/plugin-sdk/{logger-DHIIvMxj.js → logger-CvPFVOgT.js} +2 -2
  99. package/dist/plugin-sdk/{login-C31642Ld.js → login-CCTew9bt.js} +4 -4
  100. package/dist/plugin-sdk/{login-qr--y2SG_Ue.js → login-qr-BI3Vi_wJ.js} +5 -5
  101. package/dist/plugin-sdk/{manager-2UZBMCc7.js → manager-BEoYPn7R.js} +8 -8
  102. package/dist/plugin-sdk/manager-runtime-DxclHQ4U.js +15 -0
  103. package/dist/plugin-sdk/{outbound-Ba0QUI5h.js → outbound-ByOw1K6W.js} +5 -5
  104. package/dist/plugin-sdk/{outbound-attachment-B1Laso-8.js → outbound-attachment-BzVhxRRw.js} +2 -2
  105. package/dist/plugin-sdk/{path-alias-guards-C7Vm5DZ1.js → path-alias-guards-sWayacde.js} +1 -1
  106. package/dist/plugin-sdk/{paths-DopV9PQG.js → paths-Dpg3qxcl.js} +1 -1
  107. package/dist/plugin-sdk/{pi-embedded-helpers-DnA_OCzP.js → pi-embedded-helpers-DIxXkGJf.js} +16 -16
  108. package/dist/plugin-sdk/{pi-model-discovery-DdPqXk8f.js → pi-model-discovery-DM_2uFtj.js} +1 -1
  109. package/dist/plugin-sdk/pi-model-discovery-runtime-BuzvkvNR.js +8 -0
  110. package/dist/plugin-sdk/{pi-tools.before-tool-call.runtime-DxFHiLUE.js → pi-tools.before-tool-call.runtime-w1dqL_ty.js} +4 -4
  111. package/dist/plugin-sdk/{plugins-CbCt4osF.js → plugins-C4USiH29.js} +4 -4
  112. package/dist/plugin-sdk/{proxy-env-C63mMdas.js → proxy-env-ET-rp8eg.js} +1 -1
  113. package/dist/plugin-sdk/{proxy-fetch-ChxOhWF4.js → proxy-fetch-uDXGKG3Z.js} +1 -1
  114. package/dist/plugin-sdk/{pw-ai-DpJk62D4.js → pw-ai-CyOt3RDA.js} +9 -9
  115. package/dist/plugin-sdk/{qmd-manager-Ca-iSfEE.js → qmd-manager-BySdoVR7.js} +7 -7
  116. package/dist/plugin-sdk/{query-expansion-B_Xe41Ab.js → query-expansion-C6uS-7lj.js} +4 -4
  117. package/dist/plugin-sdk/{redact-DjVX-1N3.js → redact-Bvxt1T_Q.js} +1 -1
  118. package/dist/plugin-sdk/{reply-CovBlFea.js → reply-CTCSeQqW.js} +73 -73
  119. package/dist/plugin-sdk/{resolve-outbound-target-BbrHgyUk.js → resolve-outbound-target-Bw8YNANu.js} +2 -2
  120. package/dist/plugin-sdk/{run-with-concurrency-BR1DXa8T.js → run-with-concurrency-C_KCHwvf.js} +1 -1
  121. package/dist/plugin-sdk/runtime-whatsapp-login.runtime-BxgRDkhc.js +10 -0
  122. package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-elOqrkfg.js +19 -0
  123. package/dist/plugin-sdk/{send-BvAtLLPl.js → send-BZ6nYFZr.js} +5 -5
  124. package/dist/plugin-sdk/{send-BTztm3D2.js → send-C0w6xP2x.js} +6 -6
  125. package/dist/plugin-sdk/{send-CWJUuG0i.js → send-CFf-1V89.js} +8 -8
  126. package/dist/plugin-sdk/{send-EcglC4cG.js → send-CY-Qfwia.js} +7 -7
  127. package/dist/plugin-sdk/{send-BXpXBwM_.js → send-qPyNGSe4.js} +13 -13
  128. package/dist/plugin-sdk/{session-k256LJZT.js → session-COrvpvUQ.js} +3 -3
  129. package/dist/plugin-sdk/{skill-commands-DoRqLzxm.js → skill-commands-DZqhtmiv.js} +4 -4
  130. package/dist/plugin-sdk/{skills-QudILG6e.js → skills-Cw_vXEJb.js} +6 -6
  131. package/dist/plugin-sdk/slash-commands.runtime-D67JLweo.js +13 -0
  132. package/dist/plugin-sdk/slash-dispatch.runtime-DvcpvCJ0.js +52 -0
  133. package/dist/plugin-sdk/slash-skill-commands.runtime-BM1x3azR.js +16 -0
  134. package/dist/plugin-sdk/{store-BbDQw3g6.js → store-CMHj6IIw.js} +2 -2
  135. package/dist/plugin-sdk/subagent-registry-runtime-1lbDyRzz.js +52 -0
  136. package/dist/plugin-sdk/{tables-BhvloMKN.js → tables-CSqrHsKL.js} +1 -1
  137. package/dist/plugin-sdk/{thinking-URzkT-3p.js → thinking-DOnsR_A8.js} +7 -7
  138. package/dist/plugin-sdk/{tokens-DgNRBwIg.js → tokens-BDr0Z9o3.js} +1 -1
  139. package/dist/plugin-sdk/{tool-images-xpqbP6RR.js → tool-images-eEfOVkzf.js} +2 -2
  140. package/dist/plugin-sdk/web-BLyT64pW.js +56 -0
  141. package/dist/plugin-sdk/{whatsapp-actions-RcZ6vp61.js → whatsapp-actions-xcleMoMv.js} +17 -17
  142. package/dist/plugin-sdk/whatsapp.js +50 -50
  143. package/dist/plugin-sdk/zalo.js +2 -2
  144. package/dist/{plugins-CWkRQYDj.js → plugins-4Rj4OjLY.js} +11 -11
  145. package/dist/{proxy-env-Cq5gdrbj.js → proxy-env-DlmzDx8x.js} +1 -1
  146. package/dist/{plugin-sdk/proxy-fetch-Ch95c_Y2.js → proxy-fetch-B2pEfjbR.js} +1 -1
  147. package/dist/{pw-ai-GcYO6HPE.js → pw-ai-CmphSzHx.js} +1 -1
  148. package/dist/{pw-ai-Cl1Lc7RC.js → pw-ai-DNMjFMqH.js} +14 -14
  149. package/dist/{qmd-manager-BsYsO9Ii.js → qmd-manager-BtIKUaO9.js} +10 -10
  150. package/dist/{query-expansion-DtLc3wjL.js → query-expansion-CX-1fS52.js} +6 -6
  151. package/dist/{plugin-sdk/redact-hp9TOulW.js → redact-COik8ET1.js} +1 -1
  152. package/dist/{run-with-concurrency-D_ZpbgEG.js → run-with-concurrency-BgYfgkXT.js} +4 -4
  153. package/dist/runtime-whatsapp-login.runtime-DUb55byQ.js +13 -0
  154. package/dist/runtime-whatsapp-outbound.runtime-Bii_xSfI.js +22 -0
  155. package/dist/{send-Dx2RkUOZ.js → send-6lz6rNVP.js} +6 -6
  156. package/dist/{send-vmONuVgL.js → send-BHTiZcH3.js} +26 -26
  157. package/dist/{send-Bj776ESJ.js → send-L7gRiwyd.js} +7 -7
  158. package/dist/{send-DcxmcFi_.js → send-PE6cwoTe.js} +8 -8
  159. package/dist/{send-BQERFNyo.js → send-dfu6_rgf.js} +5 -5
  160. package/dist/{session-A4QhBRvH.js → session-D8ImowSs.js} +8 -8
  161. package/dist/{skill-commands-CMzBZKG2.js → skill-commands-DNqJ-kwn.js} +9 -9
  162. package/dist/{skills-CE_iqvM5.js → skills-7ODkHQYp.js} +22 -22
  163. package/dist/slash-commands.runtime-CVw6566g.js +16 -0
  164. package/dist/{slash-dispatch.runtime-Dh053pQK.js → slash-dispatch.runtime-131yup2e.js} +6 -6
  165. package/dist/slash-dispatch.runtime-B9Ygtzi4.js +56 -0
  166. package/dist/slash-skill-commands.runtime-DxZ4z5h6.js +20 -0
  167. package/dist/{store--eR1R_UX.js → store-D89wDcz9.js} +2 -2
  168. package/dist/subagent-registry-runtime-DL1Wv7nA.js +56 -0
  169. package/dist/{subagent-registry-runtime-DSi5mnCQ.js → subagent-registry-runtime-DbSf_Je6.js} +6 -6
  170. package/dist/{subsystem-Di1z8l0Z.js → subsystem-B45WV3qB.js} +14 -14
  171. package/dist/{tables-d739Y1xW.js → tables-mE4cJBN2.js} +1 -1
  172. package/dist/{target-errors-CBI2Ga0y.js → target-errors-mnlwhAjP.js} +2 -2
  173. package/dist/{thinking-DXYisHiZ.js → thinking-BeGmb5k6.js} +7 -7
  174. package/dist/{tokens-DxnY9ui_.js → tokens-q32vI39c.js} +1 -1
  175. package/dist/{tool-images-2cBx1W8h.js → tool-images-RZdHiZcG.js} +2 -2
  176. package/dist/{web-CzWRVmFt.js → web-Btj-e8kN.js} +55 -55
  177. package/dist/{web-1hWJDzNA.js → web-MR9d7KyB.js} +6 -6
  178. package/dist/{whatsapp-actions-iEArE_Ez.js → whatsapp-actions-BHbJJyqw.js} +21 -21
  179. package/dist/{workspace-CUVC6GX1.js → workspace-U-DyR64O.js} +20 -20
  180. package/package.json +6 -5
  181. package/scripts/create-instance.sh +18 -15
  182. package/scripts/install-maca.sh +1 -1
  183. package/ui/index.html +16 -0
  184. package/ui/node_modules/.bin/jiti +21 -0
  185. package/ui/node_modules/.bin/lessc +21 -0
  186. package/ui/node_modules/.bin/marked +21 -0
  187. package/ui/node_modules/.bin/playwright +21 -0
  188. package/ui/node_modules/.bin/sass +21 -0
  189. package/ui/node_modules/.bin/tsx +21 -0
  190. package/ui/node_modules/.bin/vite +21 -0
  191. package/ui/node_modules/.bin/vitest +21 -0
  192. package/ui/node_modules/.bin/yaml +21 -0
  193. package/ui/package.json +27 -0
  194. package/ui/public/apple-touch-icon.png +0 -0
  195. package/ui/public/favicon-32.png +0 -0
  196. package/ui/public/favicon.ico +0 -0
  197. package/ui/public/favicon.svg +22 -0
  198. package/ui/src/css.d.ts +1 -0
  199. package/ui/src/i18n/index.ts +3 -0
  200. package/ui/src/i18n/lib/lit-controller.ts +22 -0
  201. package/ui/src/i18n/lib/registry.ts +64 -0
  202. package/ui/src/i18n/lib/translate.ts +123 -0
  203. package/ui/src/i18n/lib/types.ts +9 -0
  204. package/ui/src/i18n/locales/de.ts +129 -0
  205. package/ui/src/i18n/locales/en.ts +337 -0
  206. package/ui/src/i18n/locales/pt-BR.ts +128 -0
  207. package/ui/src/i18n/locales/zh-CN.ts +330 -0
  208. package/ui/src/i18n/locales/zh-TW.ts +125 -0
  209. package/ui/src/i18n/test/translate.test.ts +56 -0
  210. package/ui/src/main.ts +2 -0
  211. package/ui/src/styles/base.css +385 -0
  212. package/ui/src/styles/chat/grouped.css +300 -0
  213. package/ui/src/styles/chat/layout.css +481 -0
  214. package/ui/src/styles/chat/sidebar.css +117 -0
  215. package/ui/src/styles/chat/text.css +146 -0
  216. package/ui/src/styles/chat/tool-cards.css +202 -0
  217. package/ui/src/styles/chat.css +5 -0
  218. package/ui/src/styles/components.css +2612 -0
  219. package/ui/src/styles/config.css +1658 -0
  220. package/ui/src/styles/layout.css +621 -0
  221. package/ui/src/styles/layout.mobile.css +374 -0
  222. package/ui/src/styles.css +5 -0
  223. package/ui/src/ui/__screenshots__/config-form.browser.test.ts/config-form-renderer-flags-unsupported-unions-1.png +0 -0
  224. package/ui/src/ui/__screenshots__/config-form.browser.test.ts/config-form-renderer-renders-inputs-and-patches-values-1.png +0 -0
  225. package/ui/src/ui/__screenshots__/config-form.browser.test.ts/config-form-renderer-renders-union-literals-as-select-options-1.png +0 -0
  226. package/ui/src/ui/__screenshots__/navigation.browser.test.ts/control-UI-routing-auto-scrolls-chat-history-to-the-latest-message-1.png +0 -0
  227. package/ui/src/ui/app-channels.ts +279 -0
  228. package/ui/src/ui/app-chat.ts +266 -0
  229. package/ui/src/ui/app-defaults.ts +50 -0
  230. package/ui/src/ui/app-events.ts +5 -0
  231. package/ui/src/ui/app-gateway.node.test.ts +229 -0
  232. package/ui/src/ui/app-gateway.ts +349 -0
  233. package/ui/src/ui/app-lifecycle.node.test.ts +44 -0
  234. package/ui/src/ui/app-lifecycle.ts +109 -0
  235. package/ui/src/ui/app-polling.ts +69 -0
  236. package/ui/src/ui/app-render-usage-tab.ts +273 -0
  237. package/ui/src/ui/app-render.helpers.node.test.ts +286 -0
  238. package/ui/src/ui/app-render.helpers.ts +574 -0
  239. package/ui/src/ui/app-render.ts +1168 -0
  240. package/ui/src/ui/app-scroll.test.ts +275 -0
  241. package/ui/src/ui/app-scroll.ts +179 -0
  242. package/ui/src/ui/app-settings.test.ts +70 -0
  243. package/ui/src/ui/app-settings.ts +440 -0
  244. package/ui/src/ui/app-tool-stream.node.test.ts +139 -0
  245. package/ui/src/ui/app-tool-stream.ts +455 -0
  246. package/ui/src/ui/app-view-state.ts +321 -0
  247. package/ui/src/ui/app.ts +621 -0
  248. package/ui/src/ui/assistant-identity.ts +23 -0
  249. package/ui/src/ui/chat/constants.ts +12 -0
  250. package/ui/src/ui/chat/copy-as-markdown.ts +97 -0
  251. package/ui/src/ui/chat/grouped-render.ts +287 -0
  252. package/ui/src/ui/chat/message-extract.test.ts +64 -0
  253. package/ui/src/ui/chat/message-extract.ts +122 -0
  254. package/ui/src/ui/chat/message-normalizer.test.ts +179 -0
  255. package/ui/src/ui/chat/message-normalizer.ts +101 -0
  256. package/ui/src/ui/chat/tool-cards.ts +156 -0
  257. package/ui/src/ui/chat/tool-helpers.test.ts +141 -0
  258. package/ui/src/ui/chat/tool-helpers.ts +37 -0
  259. package/ui/src/ui/chat-event-reload.test.ts +47 -0
  260. package/ui/src/ui/chat-event-reload.ts +16 -0
  261. package/ui/src/ui/chat-markdown.browser.test.ts +37 -0
  262. package/ui/src/ui/components/resizable-divider.ts +110 -0
  263. package/ui/src/ui/config-form.browser.test.ts +443 -0
  264. package/ui/src/ui/controllers/agent-files.ts +126 -0
  265. package/ui/src/ui/controllers/agent-identity.ts +59 -0
  266. package/ui/src/ui/controllers/agent-skills.ts +33 -0
  267. package/ui/src/ui/controllers/agents.test.ts +61 -0
  268. package/ui/src/ui/controllers/agents.ts +64 -0
  269. package/ui/src/ui/controllers/assistant-identity.ts +34 -0
  270. package/ui/src/ui/controllers/channels.ts +94 -0
  271. package/ui/src/ui/controllers/channels.types.ts +15 -0
  272. package/ui/src/ui/controllers/chat.test.ts +568 -0
  273. package/ui/src/ui/controllers/chat.ts +318 -0
  274. package/ui/src/ui/controllers/config/form-coerce.ts +160 -0
  275. package/ui/src/ui/controllers/config/form-utils.node.test.ts +455 -0
  276. package/ui/src/ui/controllers/config/form-utils.ts +90 -0
  277. package/ui/src/ui/controllers/config.test.ts +289 -0
  278. package/ui/src/ui/controllers/config.ts +219 -0
  279. package/ui/src/ui/controllers/control-ui-bootstrap.test.ts +82 -0
  280. package/ui/src/ui/controllers/control-ui-bootstrap.ts +49 -0
  281. package/ui/src/ui/controllers/cron-filters.test.ts +81 -0
  282. package/ui/src/ui/controllers/cron.test.ts +1070 -0
  283. package/ui/src/ui/controllers/cron.ts +921 -0
  284. package/ui/src/ui/controllers/debug.ts +60 -0
  285. package/ui/src/ui/controllers/devices.ts +159 -0
  286. package/ui/src/ui/controllers/exec-approval.ts +100 -0
  287. package/ui/src/ui/controllers/exec-approvals.ts +170 -0
  288. package/ui/src/ui/controllers/logs.ts +147 -0
  289. package/ui/src/ui/controllers/nodes.ts +32 -0
  290. package/ui/src/ui/controllers/presence.ts +37 -0
  291. package/ui/src/ui/controllers/sessions.test.ts +104 -0
  292. package/ui/src/ui/controllers/sessions.ts +127 -0
  293. package/ui/src/ui/controllers/skills.ts +157 -0
  294. package/ui/src/ui/controllers/usage.node.test.ts +181 -0
  295. package/ui/src/ui/controllers/usage.ts +315 -0
  296. package/ui/src/ui/data/moonshot-kimi-k2.ts +45 -0
  297. package/ui/src/ui/device-auth.ts +73 -0
  298. package/ui/src/ui/device-identity.ts +112 -0
  299. package/ui/src/ui/external-link.test.ts +18 -0
  300. package/ui/src/ui/external-link.ts +19 -0
  301. package/ui/src/ui/focus-mode.browser.test.ts +39 -0
  302. package/ui/src/ui/format.test.ts +101 -0
  303. package/ui/src/ui/format.ts +60 -0
  304. package/ui/src/ui/gateway.ts +360 -0
  305. package/ui/src/ui/icons.ts +256 -0
  306. package/ui/src/ui/markdown.test.ts +85 -0
  307. package/ui/src/ui/markdown.ts +139 -0
  308. package/ui/src/ui/navigation.browser.test.ts +188 -0
  309. package/ui/src/ui/navigation.test.ts +189 -0
  310. package/ui/src/ui/navigation.ts +165 -0
  311. package/ui/src/ui/open-external-url.test.ts +108 -0
  312. package/ui/src/ui/open-external-url.ts +73 -0
  313. package/ui/src/ui/presenter.ts +85 -0
  314. package/ui/src/ui/storage.node.test.ts +63 -0
  315. package/ui/src/ui/storage.ts +99 -0
  316. package/ui/src/ui/test-helpers/app-mount.ts +27 -0
  317. package/ui/src/ui/text-direction.test.ts +24 -0
  318. package/ui/src/ui/text-direction.ts +30 -0
  319. package/ui/src/ui/theme-transition.ts +109 -0
  320. package/ui/src/ui/theme.ts +16 -0
  321. package/ui/src/ui/tool-display.ts +159 -0
  322. package/ui/src/ui/types/chat-types.ts +44 -0
  323. package/ui/src/ui/types.ts +627 -0
  324. package/ui/src/ui/ui-types.ts +54 -0
  325. package/ui/src/ui/usage-helpers.node.test.ts +43 -0
  326. package/ui/src/ui/usage-helpers.ts +321 -0
  327. package/ui/src/ui/usage-types.ts +22 -0
  328. package/ui/src/ui/uuid.test.ts +41 -0
  329. package/ui/src/ui/uuid.ts +57 -0
  330. package/ui/src/ui/views/agents-panels-status-files.ts +461 -0
  331. package/ui/src/ui/views/agents-panels-tools-skills.browser.test.ts +102 -0
  332. package/ui/src/ui/views/agents-panels-tools-skills.ts +537 -0
  333. package/ui/src/ui/views/agents-utils.test.ts +100 -0
  334. package/ui/src/ui/views/agents-utils.ts +502 -0
  335. package/ui/src/ui/views/agents.ts +499 -0
  336. package/ui/src/ui/views/channel-config-extras.ts +49 -0
  337. package/ui/src/ui/views/channels.config.ts +155 -0
  338. package/ui/src/ui/views/channels.discord.ts +65 -0
  339. package/ui/src/ui/views/channels.googlechat.ts +79 -0
  340. package/ui/src/ui/views/channels.imessage.ts +65 -0
  341. package/ui/src/ui/views/channels.nostr-profile-form.ts +321 -0
  342. package/ui/src/ui/views/channels.nostr.ts +237 -0
  343. package/ui/src/ui/views/channels.shared.ts +38 -0
  344. package/ui/src/ui/views/channels.signal.ts +69 -0
  345. package/ui/src/ui/views/channels.slack.ts +65 -0
  346. package/ui/src/ui/views/channels.telegram.ts +120 -0
  347. package/ui/src/ui/views/channels.ts +325 -0
  348. package/ui/src/ui/views/channels.types.ts +62 -0
  349. package/ui/src/ui/views/channels.whatsapp.ts +118 -0
  350. package/ui/src/ui/views/chat-image-open.browser.test.ts +70 -0
  351. package/ui/src/ui/views/chat.test.ts +227 -0
  352. package/ui/src/ui/views/chat.ts +616 -0
  353. package/ui/src/ui/views/config-form.analyze.ts +267 -0
  354. package/ui/src/ui/views/config-form.node.ts +1073 -0
  355. package/ui/src/ui/views/config-form.render.ts +478 -0
  356. package/ui/src/ui/views/config-form.search.node.test.ts +69 -0
  357. package/ui/src/ui/views/config-form.shared.ts +96 -0
  358. package/ui/src/ui/views/config-form.ts +4 -0
  359. package/ui/src/ui/views/config-search.node.test.ts +50 -0
  360. package/ui/src/ui/views/config-search.ts +92 -0
  361. package/ui/src/ui/views/config.browser.test.ts +233 -0
  362. package/ui/src/ui/views/config.ts +820 -0
  363. package/ui/src/ui/views/cron.test.ts +741 -0
  364. package/ui/src/ui/views/cron.ts +1758 -0
  365. package/ui/src/ui/views/debug.ts +151 -0
  366. package/ui/src/ui/views/exec-approval.ts +89 -0
  367. package/ui/src/ui/views/gateway-url-confirmation.ts +40 -0
  368. package/ui/src/ui/views/instances.ts +89 -0
  369. package/ui/src/ui/views/logs.ts +155 -0
  370. package/ui/src/ui/views/markdown-sidebar.ts +40 -0
  371. package/ui/src/ui/views/nodes-exec-approvals.ts +617 -0
  372. package/ui/src/ui/views/nodes-shared.ts +67 -0
  373. package/ui/src/ui/views/nodes.ts +485 -0
  374. package/ui/src/ui/views/overview-hints.ts +16 -0
  375. package/ui/src/ui/views/overview.node.test.ts +39 -0
  376. package/ui/src/ui/views/overview.ts +361 -0
  377. package/ui/src/ui/views/sessions.test.ts +81 -0
  378. package/ui/src/ui/views/sessions.ts +321 -0
  379. package/ui/src/ui/views/skills-grouping.ts +40 -0
  380. package/ui/src/ui/views/skills-shared.ts +52 -0
  381. package/ui/src/ui/views/skills.ts +192 -0
  382. package/ui/src/ui/views/usage-metrics.ts +578 -0
  383. package/ui/src/ui/views/usage-query.ts +277 -0
  384. package/ui/src/ui/views/usage-render-details.test.ts +136 -0
  385. package/ui/src/ui/views/usage-render-details.ts +1083 -0
  386. package/ui/src/ui/views/usage-render-overview.ts +796 -0
  387. package/ui/src/ui/views/usage-styles/usageStyles-part1.ts +701 -0
  388. package/ui/src/ui/views/usage-styles/usageStyles-part2.ts +702 -0
  389. package/ui/src/ui/views/usage-styles/usageStyles-part3.ts +551 -0
  390. package/ui/src/ui/views/usage.ts +836 -0
  391. package/ui/src/ui/views/usageStyles.ts +5 -0
  392. package/ui/src/ui/views/usageTypes.ts +105 -0
  393. package/ui/vite.config.ts +43 -0
  394. package/ui/vitest.config.ts +15 -0
  395. package/ui/vitest.node.config.ts +10 -0
  396. package/dist/deliver-runtime-P-G3bPjW.js +0 -36
  397. package/dist/deps-send-discord.runtime-DnbhTFX9.js +0 -26
  398. package/dist/deps-send-imessage.runtime-BOiQ6mDx.js +0 -25
  399. package/dist/deps-send-signal.runtime-CTcl388M.js +0 -24
  400. package/dist/deps-send-slack.runtime-CCqBz4Kg.js +0 -22
  401. package/dist/deps-send-telegram.runtime-DGSKTCpH.js +0 -27
  402. package/dist/deps-send-whatsapp.runtime-CJkTHkah.js +0 -60
  403. package/dist/errors-CCLeFWAg.js +0 -54
  404. package/dist/image-runtime-CVv2ra9J.js +0 -29
  405. package/dist/manager-runtime-BN6VevdC.js +0 -18
  406. package/dist/pi-model-discovery-BGgOlX8N.js +0 -134
  407. package/dist/pi-model-discovery-runtime-Bwmi4Ev8.js +0 -11
  408. package/dist/plugin-sdk/accounts-CJWOBzwB.js +0 -35
  409. package/dist/plugin-sdk/accounts-DP1-L-QS.js +0 -288
  410. package/dist/plugin-sdk/accounts-DZhWlEg3.js +0 -46
  411. package/dist/plugin-sdk/active-listener-CftX5jLD.js +0 -50
  412. package/dist/plugin-sdk/api-key-rotation-BRE4X2tf.js +0 -181
  413. package/dist/plugin-sdk/audio-preflight-DGEUDxxR.js +0 -69
  414. package/dist/plugin-sdk/audio-transcription-runner-DkoPNPYt.js +0 -2176
  415. package/dist/plugin-sdk/audit-membership-runtime-DSBHHw7o.js +0 -58
  416. package/dist/plugin-sdk/channel-activity-F3d0yUwy.js +0 -94
  417. package/dist/plugin-sdk/channel-web-QF7EpjeP.js +0 -2256
  418. package/dist/plugin-sdk/chrome-BXoCyCkY.js +0 -2415
  419. package/dist/plugin-sdk/commands-registry-t7cXBTfN.js +0 -1125
  420. package/dist/plugin-sdk/config-BkEnz2Po.js +0 -17913
  421. package/dist/plugin-sdk/deliver-B6AG_l67.js +0 -1694
  422. package/dist/plugin-sdk/deliver-runtime-BFdqklJM.js +0 -32
  423. package/dist/plugin-sdk/deliver-runtime-D585kJZc.js +0 -32
  424. package/dist/plugin-sdk/deps-send-discord.runtime-DuqpYwU0.js +0 -23
  425. package/dist/plugin-sdk/deps-send-discord.runtime-a_OKY2js.js +0 -23
  426. package/dist/plugin-sdk/deps-send-imessage.runtime-Baxy9TD4.js +0 -22
  427. package/dist/plugin-sdk/deps-send-imessage.runtime-CZ2rS8Lb.js +0 -22
  428. package/dist/plugin-sdk/deps-send-signal.runtime-BdqiWhIh.js +0 -21
  429. package/dist/plugin-sdk/deps-send-signal.runtime-BwXoCrFl.js +0 -21
  430. package/dist/plugin-sdk/deps-send-slack.runtime-04s36qiC.js +0 -19
  431. package/dist/plugin-sdk/deps-send-slack.runtime-CLmKjgso.js +0 -19
  432. package/dist/plugin-sdk/deps-send-telegram.runtime-BKfdBKnZ.js +0 -24
  433. package/dist/plugin-sdk/deps-send-telegram.runtime-LE5tkPvr.js +0 -24
  434. package/dist/plugin-sdk/deps-send-whatsapp.runtime-BOTwkbx_.js +0 -57
  435. package/dist/plugin-sdk/deps-send-whatsapp.runtime-Bz57lobC.js +0 -57
  436. package/dist/plugin-sdk/diagnostic-CsP-lEkI.js +0 -319
  437. package/dist/plugin-sdk/fetch-guard-DETCcJzQ.js +0 -156
  438. package/dist/plugin-sdk/fs-safe-B8y811FR.js +0 -352
  439. package/dist/plugin-sdk/image-DjTEkYZE.js +0 -2310
  440. package/dist/plugin-sdk/image-ops-BSiMpAw4.js +0 -584
  441. package/dist/plugin-sdk/image-runtime-6xPp8m5a.js +0 -25
  442. package/dist/plugin-sdk/image-runtime-B8twoubs.js +0 -25
  443. package/dist/plugin-sdk/ir-DQ7_HbvK.js +0 -1296
  444. package/dist/plugin-sdk/local-roots-BUP4YBmR.js +0 -186
  445. package/dist/plugin-sdk/logger-CZY9KIoY.js +0 -1163
  446. package/dist/plugin-sdk/login-BxEKLlCo.js +0 -57
  447. package/dist/plugin-sdk/login-qr-BQIpMPr9.js +0 -320
  448. package/dist/plugin-sdk/manager-I6KbPihW.js +0 -3917
  449. package/dist/plugin-sdk/manager-runtime-CFfYYWIQ.js +0 -15
  450. package/dist/plugin-sdk/manager-runtime-CMeLwose.js +0 -15
  451. package/dist/plugin-sdk/outbound-NS6UHnB6.js +0 -212
  452. package/dist/plugin-sdk/outbound-attachment-Dy6fyf6H.js +0 -19
  453. package/dist/plugin-sdk/path-alias-guards-DBjLbIX_.js +0 -43
  454. package/dist/plugin-sdk/paths-vTM3Lh3X.js +0 -166
  455. package/dist/plugin-sdk/pi-embedded-helpers-1R1gu7eX.js +0 -9627
  456. package/dist/plugin-sdk/pi-model-discovery-runtime-D8CJhtJY.js +0 -8
  457. package/dist/plugin-sdk/pi-model-discovery-runtime-Do9o-dUd.js +0 -8
  458. package/dist/plugin-sdk/pi-tools.before-tool-call.runtime-D4sFsIks.js +0 -354
  459. package/dist/plugin-sdk/plugins-DeBZB9l_.js +0 -864
  460. package/dist/plugin-sdk/pw-ai-DEOmCSSC.js +0 -1938
  461. package/dist/plugin-sdk/qmd-manager-HyYKoEch.js +0 -1448
  462. package/dist/plugin-sdk/query-expansion-CeyKUeDW.js +0 -1011
  463. package/dist/plugin-sdk/reply-DAo_Jt8K.js +0 -97916
  464. package/dist/plugin-sdk/resolve-outbound-target-B42qgQS9.js +0 -40
  465. package/dist/plugin-sdk/run-with-concurrency-Bt_ks0Qa.js +0 -1994
  466. package/dist/plugin-sdk/runtime-whatsapp-login.runtime-B6W989eF.js +0 -10
  467. package/dist/plugin-sdk/runtime-whatsapp-login.runtime-SkO91TZH.js +0 -10
  468. package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-B0VWK5hm.js +0 -19
  469. package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-c_GDFy37.js +0 -19
  470. package/dist/plugin-sdk/send-CQpMudwO.js +0 -2587
  471. package/dist/plugin-sdk/send-DQHLzVyO.js +0 -414
  472. package/dist/plugin-sdk/send-DTB24bEF.js +0 -3135
  473. package/dist/plugin-sdk/send-DfHadjZ_.js +0 -503
  474. package/dist/plugin-sdk/send-XXlW2iny.js +0 -540
  475. package/dist/plugin-sdk/session-6TF6MyaC.js +0 -169
  476. package/dist/plugin-sdk/skill-commands-CkGeFUMl.js +0 -342
  477. package/dist/plugin-sdk/skills-CBkHBYPq.js +0 -1428
  478. package/dist/plugin-sdk/slash-commands.runtime-CxliuGaP.js +0 -13
  479. package/dist/plugin-sdk/slash-commands.runtime-DS6vCNSL.js +0 -13
  480. package/dist/plugin-sdk/slash-dispatch.runtime-BXrxb2wd.js +0 -52
  481. package/dist/plugin-sdk/slash-dispatch.runtime-DFaeYlJQ.js +0 -52
  482. package/dist/plugin-sdk/slash-skill-commands.runtime-0M0OLCxq.js +0 -16
  483. package/dist/plugin-sdk/slash-skill-commands.runtime-Bd6qQ2oT.js +0 -16
  484. package/dist/plugin-sdk/ssrf-cFtplYtS.js +0 -202
  485. package/dist/plugin-sdk/store-5nyxY3WU.js +0 -81
  486. package/dist/plugin-sdk/subagent-registry-runtime-1uwQbuXj.js +0 -52
  487. package/dist/plugin-sdk/subagent-registry-runtime-DCtmDwna.js +0 -52
  488. package/dist/plugin-sdk/tables-C47P4GTN.js +0 -55
  489. package/dist/plugin-sdk/target-errors-Blia4S69.js +0 -195
  490. package/dist/plugin-sdk/thinking-Bo2eosVa.js +0 -1206
  491. package/dist/plugin-sdk/tokens-B1PW5Ayy.js +0 -52
  492. package/dist/plugin-sdk/tool-images-Gk_-0y2N.js +0 -274
  493. package/dist/plugin-sdk/web-B74yhL2N.js +0 -56
  494. package/dist/plugin-sdk/web-CVxZbXyH.js +0 -56
  495. package/dist/plugin-sdk/whatsapp-actions-Bw0H9g-n.js +0 -80
  496. package/dist/proxy-fetch-CCjEYbFm.js +0 -38
  497. package/dist/redact-ClbcYG1J.js +0 -319
  498. package/dist/runtime-whatsapp-login.runtime-IeylZEl4.js +0 -13
  499. package/dist/runtime-whatsapp-outbound.runtime-ClBRuLsq.js +0 -22
  500. package/dist/slash-commands.runtime-Cpn2tYW4.js +0 -16
  501. package/dist/slash-dispatch.runtime-DoBAQBU5.js +0 -56
  502. package/dist/slash-skill-commands.runtime-DKMvvdDW.js +0 -20
  503. package/dist/subagent-registry-runtime-ppWS3tVu.js +0 -56
@@ -0,0 +1,1070 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { DEFAULT_CRON_FORM } from "../app-defaults.ts";
3
+ import {
4
+ addCronJob,
5
+ cancelCronEdit,
6
+ loadCronJobsPage,
7
+ loadCronRuns,
8
+ loadMoreCronRuns,
9
+ normalizeCronFormState,
10
+ runCronJob,
11
+ startCronEdit,
12
+ startCronClone,
13
+ validateCronForm,
14
+ type CronState,
15
+ } from "./cron.ts";
16
+
17
+ function createState(overrides: Partial<CronState> = {}): CronState {
18
+ return {
19
+ client: null,
20
+ connected: true,
21
+ cronLoading: false,
22
+ cronJobsLoadingMore: false,
23
+ cronJobs: [],
24
+ cronJobsTotal: 0,
25
+ cronJobsHasMore: false,
26
+ cronJobsNextOffset: null,
27
+ cronJobsLimit: 50,
28
+ cronJobsQuery: "",
29
+ cronJobsEnabledFilter: "all",
30
+ cronJobsScheduleKindFilter: "all",
31
+ cronJobsLastStatusFilter: "all",
32
+ cronJobsSortBy: "nextRunAtMs",
33
+ cronJobsSortDir: "asc",
34
+ cronStatus: null,
35
+ cronError: null,
36
+ cronForm: { ...DEFAULT_CRON_FORM },
37
+ cronFieldErrors: {},
38
+ cronEditingJobId: null,
39
+ cronRunsJobId: null,
40
+ cronRunsLoadingMore: false,
41
+ cronRuns: [],
42
+ cronRunsTotal: 0,
43
+ cronRunsHasMore: false,
44
+ cronRunsNextOffset: null,
45
+ cronRunsLimit: 50,
46
+ cronRunsScope: "all",
47
+ cronRunsStatuses: [],
48
+ cronRunsDeliveryStatuses: [],
49
+ cronRunsStatusFilter: "all",
50
+ cronRunsQuery: "",
51
+ cronRunsSortDir: "desc",
52
+ cronBusy: false,
53
+ ...overrides,
54
+ };
55
+ }
56
+
57
+ describe("cron controller", () => {
58
+ it("normalizes stale announce mode when session/payload no longer support announce", () => {
59
+ const normalized = normalizeCronFormState({
60
+ ...DEFAULT_CRON_FORM,
61
+ sessionTarget: "main",
62
+ payloadKind: "systemEvent",
63
+ deliveryMode: "announce",
64
+ });
65
+
66
+ expect(normalized.deliveryMode).toBe("none");
67
+ });
68
+
69
+ it("keeps announce mode when isolated agentTurn supports announce", () => {
70
+ const normalized = normalizeCronFormState({
71
+ ...DEFAULT_CRON_FORM,
72
+ sessionTarget: "isolated",
73
+ payloadKind: "agentTurn",
74
+ deliveryMode: "announce",
75
+ });
76
+
77
+ expect(normalized.deliveryMode).toBe("announce");
78
+ });
79
+
80
+ it("forwards webhook delivery in cron.add payload", async () => {
81
+ const request = vi.fn(async (method: string, _payload?: unknown) => {
82
+ if (method === "cron.add") {
83
+ return { id: "job-1" };
84
+ }
85
+ if (method === "cron.list") {
86
+ return { jobs: [] };
87
+ }
88
+ if (method === "cron.status") {
89
+ return { enabled: true, jobs: 0, nextWakeAtMs: null };
90
+ }
91
+ return {};
92
+ });
93
+
94
+ const state = createState({
95
+ client: {
96
+ request,
97
+ } as unknown as CronState["client"],
98
+ cronForm: {
99
+ ...DEFAULT_CRON_FORM,
100
+ name: "webhook job",
101
+ scheduleKind: "every",
102
+ everyAmount: "1",
103
+ everyUnit: "minutes",
104
+ sessionTarget: "isolated",
105
+ wakeMode: "next-heartbeat",
106
+ payloadKind: "agentTurn",
107
+ payloadText: "run this",
108
+ deliveryMode: "webhook",
109
+ deliveryTo: "https://example.invalid/cron",
110
+ },
111
+ });
112
+
113
+ await addCronJob(state);
114
+
115
+ const addCall = request.mock.calls.find(([method]) => method === "cron.add");
116
+ expect(addCall).toBeDefined();
117
+ expect(addCall?.[1]).toMatchObject({
118
+ name: "webhook job",
119
+ delivery: { mode: "webhook", to: "https://example.invalid/cron" },
120
+ });
121
+ });
122
+
123
+ it("forwards sessionKey and delivery accountId in cron.add payload", async () => {
124
+ const request = vi.fn(async (method: string, _payload?: unknown) => {
125
+ if (method === "cron.add") {
126
+ return { id: "job-3" };
127
+ }
128
+ if (method === "cron.list") {
129
+ return { jobs: [] };
130
+ }
131
+ if (method === "cron.status") {
132
+ return { enabled: true, jobs: 0, nextWakeAtMs: null };
133
+ }
134
+ return {};
135
+ });
136
+
137
+ const state = createState({
138
+ client: { request } as unknown as CronState["client"],
139
+ cronForm: {
140
+ ...DEFAULT_CRON_FORM,
141
+ name: "account-routed",
142
+ scheduleKind: "cron",
143
+ cronExpr: "0 * * * *",
144
+ sessionTarget: "isolated",
145
+ payloadKind: "agentTurn",
146
+ payloadText: "run this",
147
+ sessionKey: "agent:ops:main",
148
+ deliveryMode: "announce",
149
+ deliveryAccountId: "ops-bot",
150
+ },
151
+ });
152
+
153
+ await addCronJob(state);
154
+
155
+ const addCall = request.mock.calls.find(([method]) => method === "cron.add");
156
+ expect(addCall).toBeDefined();
157
+ expect(addCall?.[1]).toMatchObject({
158
+ sessionKey: "agent:ops:main",
159
+ delivery: { mode: "announce", accountId: "ops-bot" },
160
+ });
161
+ });
162
+
163
+ it("forwards lightContext in cron payload", async () => {
164
+ const request = vi.fn(async (method: string, _payload?: unknown) => {
165
+ if (method === "cron.add") {
166
+ return { id: "job-light" };
167
+ }
168
+ if (method === "cron.list") {
169
+ return { jobs: [] };
170
+ }
171
+ if (method === "cron.status") {
172
+ return { enabled: true, jobs: 0, nextWakeAtMs: null };
173
+ }
174
+ return {};
175
+ });
176
+
177
+ const state = createState({
178
+ client: { request } as unknown as CronState["client"],
179
+ cronForm: {
180
+ ...DEFAULT_CRON_FORM,
181
+ name: "light-context job",
182
+ scheduleKind: "cron",
183
+ cronExpr: "0 * * * *",
184
+ sessionTarget: "isolated",
185
+ payloadKind: "agentTurn",
186
+ payloadText: "run this",
187
+ payloadLightContext: true,
188
+ },
189
+ });
190
+
191
+ await addCronJob(state);
192
+
193
+ const addCall = request.mock.calls.find(([method]) => method === "cron.add");
194
+ expect(addCall).toBeDefined();
195
+ expect(addCall?.[1]).toMatchObject({
196
+ payload: { kind: "agentTurn", lightContext: true },
197
+ });
198
+ });
199
+
200
+ it('sends delivery: { mode: "none" } explicitly in cron.add payload', async () => {
201
+ const request = vi.fn(async (method: string, _payload?: unknown) => {
202
+ if (method === "cron.add") {
203
+ return { id: "job-none-add" };
204
+ }
205
+ if (method === "cron.list") {
206
+ return { jobs: [] };
207
+ }
208
+ if (method === "cron.status") {
209
+ return { enabled: true, jobs: 0, nextWakeAtMs: null };
210
+ }
211
+ return {};
212
+ });
213
+
214
+ const state = createState({
215
+ client: {
216
+ request,
217
+ } as unknown as CronState["client"],
218
+ cronForm: {
219
+ ...DEFAULT_CRON_FORM,
220
+ name: "none delivery job",
221
+ scheduleKind: "every",
222
+ everyAmount: "1",
223
+ everyUnit: "minutes",
224
+ sessionTarget: "isolated",
225
+ wakeMode: "next-heartbeat",
226
+ payloadKind: "agentTurn",
227
+ payloadText: "run this",
228
+ deliveryMode: "none",
229
+ },
230
+ });
231
+
232
+ await addCronJob(state);
233
+
234
+ const addCall = request.mock.calls.find(([method]) => method === "cron.add");
235
+ expect(addCall).toBeDefined();
236
+ expect((addCall?.[1] as { delivery?: unknown } | undefined)?.delivery).toEqual({
237
+ mode: "none",
238
+ });
239
+ });
240
+
241
+ it('sends delivery: { mode: "none" } explicitly in cron.update patch', async () => {
242
+ const request = vi.fn(async (method: string, _payload?: unknown) => {
243
+ if (method === "cron.update") {
244
+ return { id: "job-none-update" };
245
+ }
246
+ if (method === "cron.list") {
247
+ return { jobs: [{ id: "job-none-update" }] };
248
+ }
249
+ if (method === "cron.status") {
250
+ return { enabled: true, jobs: 1, nextWakeAtMs: null };
251
+ }
252
+ return {};
253
+ });
254
+
255
+ const state = createState({
256
+ client: {
257
+ request,
258
+ } as unknown as CronState["client"],
259
+ cronEditingJobId: "job-none-update",
260
+ cronForm: {
261
+ ...DEFAULT_CRON_FORM,
262
+ name: "switch to none",
263
+ scheduleKind: "every",
264
+ everyAmount: "30",
265
+ everyUnit: "minutes",
266
+ sessionTarget: "isolated",
267
+ wakeMode: "next-heartbeat",
268
+ payloadKind: "agentTurn",
269
+ payloadText: "do work",
270
+ deliveryMode: "none",
271
+ },
272
+ });
273
+
274
+ await addCronJob(state);
275
+
276
+ const updateCall = request.mock.calls.find(([method]) => method === "cron.update");
277
+ expect(updateCall).toBeDefined();
278
+ expect(
279
+ (updateCall?.[1] as { patch?: { delivery?: unknown } } | undefined)?.patch?.delivery,
280
+ ).toEqual({
281
+ mode: "none",
282
+ });
283
+ });
284
+
285
+ it("does not submit stale announce delivery when unsupported", async () => {
286
+ const request = vi.fn(async (method: string, _payload?: unknown) => {
287
+ if (method === "cron.add") {
288
+ return { id: "job-2" };
289
+ }
290
+ if (method === "cron.list") {
291
+ return { jobs: [] };
292
+ }
293
+ if (method === "cron.status") {
294
+ return { enabled: true, jobs: 0, nextWakeAtMs: null };
295
+ }
296
+ return {};
297
+ });
298
+
299
+ const state = createState({
300
+ client: {
301
+ request,
302
+ } as unknown as CronState["client"],
303
+ cronForm: {
304
+ ...DEFAULT_CRON_FORM,
305
+ name: "main job",
306
+ scheduleKind: "every",
307
+ everyAmount: "1",
308
+ everyUnit: "minutes",
309
+ sessionTarget: "main",
310
+ wakeMode: "next-heartbeat",
311
+ payloadKind: "systemEvent",
312
+ payloadText: "run this",
313
+ deliveryMode: "announce",
314
+ deliveryTo: "buddy",
315
+ },
316
+ });
317
+
318
+ await addCronJob(state);
319
+
320
+ const addCall = request.mock.calls.find(([method]) => method === "cron.add");
321
+ expect(addCall).toBeDefined();
322
+ expect(addCall?.[1]).toMatchObject({
323
+ name: "main job",
324
+ });
325
+ // Delivery is explicitly sent as { mode: "none" } to clear the announce delivery on the backend.
326
+ // Previously this was sent as undefined, which left announce in place (bug #31075).
327
+ expect((addCall?.[1] as { delivery?: unknown } | undefined)?.delivery).toEqual({
328
+ mode: "none",
329
+ });
330
+ // After submit, form is reset to defaults (deliveryMode = "announce" from DEFAULT_CRON_FORM).
331
+ expect(state.cronForm.deliveryMode).toBe("announce");
332
+ });
333
+
334
+ it("submits cron.update when editing an existing job", async () => {
335
+ const request = vi.fn(async (method: string, _payload?: unknown) => {
336
+ if (method === "cron.update") {
337
+ return { id: "job-1" };
338
+ }
339
+ if (method === "cron.list") {
340
+ return { jobs: [{ id: "job-1" }] };
341
+ }
342
+ if (method === "cron.status") {
343
+ return { enabled: true, jobs: 1, nextWakeAtMs: null };
344
+ }
345
+ return {};
346
+ });
347
+
348
+ const state = createState({
349
+ client: {
350
+ request,
351
+ } as unknown as CronState["client"],
352
+ cronEditingJobId: "job-1",
353
+ cronForm: {
354
+ ...DEFAULT_CRON_FORM,
355
+ name: "edited job",
356
+ description: "",
357
+ clearAgent: true,
358
+ deleteAfterRun: false,
359
+ scheduleKind: "cron",
360
+ cronExpr: "0 8 * * *",
361
+ scheduleExact: true,
362
+ payloadKind: "systemEvent",
363
+ payloadText: "updated",
364
+ deliveryMode: "none",
365
+ },
366
+ });
367
+
368
+ await addCronJob(state);
369
+
370
+ const updateCall = request.mock.calls.find(([method]) => method === "cron.update");
371
+ expect(updateCall).toBeDefined();
372
+ expect(updateCall?.[1]).toMatchObject({
373
+ id: "job-1",
374
+ patch: {
375
+ name: "edited job",
376
+ description: "",
377
+ agentId: null,
378
+ deleteAfterRun: false,
379
+ schedule: { kind: "cron", expr: "0 8 * * *", staggerMs: 0 },
380
+ payload: { kind: "systemEvent", text: "updated" },
381
+ delivery: { mode: "none" },
382
+ },
383
+ });
384
+ expect(state.cronEditingJobId).toBeNull();
385
+ });
386
+
387
+ it("sends empty delivery.accountId in cron.update to clear persisted account routing", async () => {
388
+ const request = vi.fn(async (method: string, _payload?: unknown) => {
389
+ if (method === "cron.update") {
390
+ return { id: "job-clear-account-id" };
391
+ }
392
+ if (method === "cron.list") {
393
+ return { jobs: [{ id: "job-clear-account-id" }] };
394
+ }
395
+ if (method === "cron.status") {
396
+ return { enabled: true, jobs: 1, nextWakeAtMs: null };
397
+ }
398
+ return {};
399
+ });
400
+
401
+ const state = createState({
402
+ client: { request } as unknown as CronState["client"],
403
+ cronEditingJobId: "job-clear-account-id",
404
+ cronJobs: [
405
+ {
406
+ id: "job-clear-account-id",
407
+ name: "clear account",
408
+ enabled: true,
409
+ createdAtMs: 0,
410
+ updatedAtMs: 0,
411
+ schedule: { kind: "cron", expr: "0 * * * *" },
412
+ sessionTarget: "isolated",
413
+ wakeMode: "next-heartbeat",
414
+ payload: { kind: "agentTurn", message: "run" },
415
+ delivery: { mode: "announce", accountId: "ops-bot" },
416
+ state: {},
417
+ },
418
+ ],
419
+ cronForm: {
420
+ ...DEFAULT_CRON_FORM,
421
+ name: "clear account",
422
+ scheduleKind: "cron",
423
+ cronExpr: "0 * * * *",
424
+ sessionTarget: "isolated",
425
+ wakeMode: "next-heartbeat",
426
+ payloadKind: "agentTurn",
427
+ payloadText: "run",
428
+ deliveryMode: "announce",
429
+ deliveryAccountId: " ",
430
+ },
431
+ });
432
+
433
+ await addCronJob(state);
434
+
435
+ const updateCall = request.mock.calls.find(([method]) => method === "cron.update");
436
+ expect(updateCall).toBeDefined();
437
+ expect(updateCall?.[1]).toMatchObject({
438
+ id: "job-clear-account-id",
439
+ patch: {
440
+ delivery: {
441
+ mode: "announce",
442
+ accountId: "",
443
+ },
444
+ },
445
+ });
446
+ });
447
+
448
+ it("maps a cron job into editable form fields", () => {
449
+ const state = createState();
450
+ const job = {
451
+ id: "job-9",
452
+ name: "Weekly report",
453
+ description: "desc",
454
+ sessionKey: "agent:ops:main",
455
+ enabled: false,
456
+ createdAtMs: 0,
457
+ updatedAtMs: 0,
458
+ schedule: { kind: "every" as const, everyMs: 7_200_000 },
459
+ sessionTarget: "isolated" as const,
460
+ wakeMode: "next-heartbeat" as const,
461
+ payload: { kind: "agentTurn" as const, message: "ship it", timeoutSeconds: 45 },
462
+ delivery: { mode: "announce" as const, channel: "telegram", to: "123", accountId: "bot-2" },
463
+ state: {},
464
+ };
465
+
466
+ startCronEdit(state, job);
467
+
468
+ expect(state.cronEditingJobId).toBe("job-9");
469
+ expect(state.cronRunsJobId).toBe("job-9");
470
+ expect(state.cronForm.name).toBe("Weekly report");
471
+ expect(state.cronForm.sessionKey).toBe("agent:ops:main");
472
+ expect(state.cronForm.enabled).toBe(false);
473
+ expect(state.cronForm.scheduleKind).toBe("every");
474
+ expect(state.cronForm.everyAmount).toBe("2");
475
+ expect(state.cronForm.everyUnit).toBe("hours");
476
+ expect(state.cronForm.payloadKind).toBe("agentTurn");
477
+ expect(state.cronForm.payloadText).toBe("ship it");
478
+ expect(state.cronForm.timeoutSeconds).toBe("45");
479
+ expect(state.cronForm.deliveryMode).toBe("announce");
480
+ expect(state.cronForm.deliveryChannel).toBe("telegram");
481
+ expect(state.cronForm.deliveryTo).toBe("123");
482
+ expect(state.cronForm.deliveryAccountId).toBe("bot-2");
483
+ });
484
+
485
+ it("includes model/thinking/stagger/bestEffort in cron.update patch", async () => {
486
+ const request = vi.fn(async (method: string, _payload?: unknown) => {
487
+ if (method === "cron.update") {
488
+ return { id: "job-2" };
489
+ }
490
+ if (method === "cron.list") {
491
+ return { jobs: [{ id: "job-2" }] };
492
+ }
493
+ if (method === "cron.status") {
494
+ return { enabled: true, jobs: 1, nextWakeAtMs: null };
495
+ }
496
+ return {};
497
+ });
498
+ const state = createState({
499
+ client: { request } as unknown as CronState["client"],
500
+ cronEditingJobId: "job-2",
501
+ cronForm: {
502
+ ...DEFAULT_CRON_FORM,
503
+ name: "advanced edit",
504
+ scheduleKind: "cron",
505
+ cronExpr: "0 9 * * *",
506
+ staggerAmount: "30",
507
+ staggerUnit: "seconds",
508
+ payloadKind: "agentTurn",
509
+ payloadText: "run it",
510
+ payloadModel: "opus",
511
+ payloadThinking: "low",
512
+ deliveryMode: "announce",
513
+ deliveryBestEffort: true,
514
+ },
515
+ });
516
+
517
+ await addCronJob(state);
518
+
519
+ const updateCall = request.mock.calls.find(([method]) => method === "cron.update");
520
+ expect(updateCall).toBeDefined();
521
+ expect(updateCall?.[1]).toMatchObject({
522
+ id: "job-2",
523
+ patch: {
524
+ schedule: { kind: "cron", expr: "0 9 * * *", staggerMs: 30_000 },
525
+ payload: {
526
+ kind: "agentTurn",
527
+ message: "run it",
528
+ model: "opus",
529
+ thinking: "low",
530
+ },
531
+ delivery: { mode: "announce", bestEffort: true },
532
+ },
533
+ });
534
+ });
535
+
536
+ it("sends lightContext=false in cron.update when clearing prior light-context setting", async () => {
537
+ const request = vi.fn(async (method: string, _payload?: unknown) => {
538
+ if (method === "cron.update") {
539
+ return { id: "job-clear-light" };
540
+ }
541
+ if (method === "cron.list") {
542
+ return { jobs: [{ id: "job-clear-light" }] };
543
+ }
544
+ if (method === "cron.status") {
545
+ return { enabled: true, jobs: 1, nextWakeAtMs: null };
546
+ }
547
+ return {};
548
+ });
549
+ const state = createState({
550
+ client: { request } as unknown as CronState["client"],
551
+ cronEditingJobId: "job-clear-light",
552
+ cronJobs: [
553
+ {
554
+ id: "job-clear-light",
555
+ name: "Light job",
556
+ enabled: true,
557
+ createdAtMs: 0,
558
+ updatedAtMs: 0,
559
+ schedule: { kind: "cron", expr: "0 9 * * *" },
560
+ sessionTarget: "isolated",
561
+ wakeMode: "now",
562
+ payload: { kind: "agentTurn", message: "run", lightContext: true },
563
+ state: {},
564
+ },
565
+ ],
566
+ cronForm: {
567
+ ...DEFAULT_CRON_FORM,
568
+ name: "Light job",
569
+ scheduleKind: "cron",
570
+ cronExpr: "0 9 * * *",
571
+ payloadKind: "agentTurn",
572
+ payloadText: "run",
573
+ payloadLightContext: false,
574
+ },
575
+ });
576
+
577
+ await addCronJob(state);
578
+
579
+ const updateCall = request.mock.calls.find(([method]) => method === "cron.update");
580
+ expect(updateCall).toBeDefined();
581
+ expect(updateCall?.[1]).toMatchObject({
582
+ id: "job-clear-light",
583
+ patch: {
584
+ payload: {
585
+ kind: "agentTurn",
586
+ lightContext: false,
587
+ },
588
+ },
589
+ });
590
+ });
591
+
592
+ it("includes custom failureAlert fields in cron.update patch", async () => {
593
+ const request = vi.fn(async (method: string, _payload?: unknown) => {
594
+ if (method === "cron.update") {
595
+ return { id: "job-alert" };
596
+ }
597
+ if (method === "cron.list") {
598
+ return { jobs: [{ id: "job-alert" }] };
599
+ }
600
+ if (method === "cron.status") {
601
+ return { enabled: true, jobs: 1, nextWakeAtMs: null };
602
+ }
603
+ return {};
604
+ });
605
+ const state = createState({
606
+ client: { request } as unknown as CronState["client"],
607
+ cronEditingJobId: "job-alert",
608
+ cronForm: {
609
+ ...DEFAULT_CRON_FORM,
610
+ name: "alert job",
611
+ payloadKind: "agentTurn",
612
+ payloadText: "run it",
613
+ failureAlertMode: "custom",
614
+ failureAlertAfter: "3",
615
+ failureAlertCooldownSeconds: "120",
616
+ failureAlertChannel: "telegram",
617
+ failureAlertTo: "123456",
618
+ },
619
+ });
620
+
621
+ await addCronJob(state);
622
+
623
+ const updateCall = request.mock.calls.find(([method]) => method === "cron.update");
624
+ expect(updateCall).toBeDefined();
625
+ expect(updateCall?.[1]).toMatchObject({
626
+ id: "job-alert",
627
+ patch: {
628
+ failureAlert: {
629
+ after: 3,
630
+ cooldownMs: 120_000,
631
+ channel: "telegram",
632
+ to: "123456",
633
+ mode: "announce",
634
+ accountId: undefined,
635
+ },
636
+ },
637
+ });
638
+ });
639
+
640
+ it("includes failure alert mode/accountId in cron.update patch", async () => {
641
+ const request = vi.fn(async (method: string, _payload?: unknown) => {
642
+ if (method === "cron.update") {
643
+ return { id: "job-alert-mode" };
644
+ }
645
+ if (method === "cron.list") {
646
+ return { jobs: [{ id: "job-alert-mode" }] };
647
+ }
648
+ if (method === "cron.status") {
649
+ return { enabled: true, jobs: 1, nextWakeAtMs: null };
650
+ }
651
+ return {};
652
+ });
653
+ const state = createState({
654
+ client: { request } as unknown as CronState["client"],
655
+ cronEditingJobId: "job-alert-mode",
656
+ cronForm: {
657
+ ...DEFAULT_CRON_FORM,
658
+ name: "alert mode job",
659
+ payloadKind: "agentTurn",
660
+ payloadText: "run it",
661
+ failureAlertMode: "custom",
662
+ failureAlertAfter: "1",
663
+ failureAlertDeliveryMode: "webhook",
664
+ failureAlertAccountId: "bot-a",
665
+ },
666
+ });
667
+
668
+ await addCronJob(state);
669
+
670
+ const updateCall = request.mock.calls.find(([method]) => method === "cron.update");
671
+ expect(updateCall).toBeDefined();
672
+ expect(updateCall?.[1]).toMatchObject({
673
+ id: "job-alert-mode",
674
+ patch: {
675
+ failureAlert: {
676
+ after: 1,
677
+ mode: "webhook",
678
+ accountId: "bot-a",
679
+ },
680
+ },
681
+ });
682
+ });
683
+
684
+ it("omits failureAlert.cooldownMs when custom cooldown is left blank", async () => {
685
+ const request = vi.fn(async (method: string, _payload?: unknown) => {
686
+ if (method === "cron.update") {
687
+ return { id: "job-alert-no-cooldown" };
688
+ }
689
+ if (method === "cron.list") {
690
+ return { jobs: [{ id: "job-alert-no-cooldown" }] };
691
+ }
692
+ if (method === "cron.status") {
693
+ return { enabled: true, jobs: 1, nextWakeAtMs: null };
694
+ }
695
+ return {};
696
+ });
697
+ const state = createState({
698
+ client: { request } as unknown as CronState["client"],
699
+ cronEditingJobId: "job-alert-no-cooldown",
700
+ cronForm: {
701
+ ...DEFAULT_CRON_FORM,
702
+ name: "alert job no cooldown",
703
+ payloadKind: "agentTurn",
704
+ payloadText: "run it",
705
+ failureAlertMode: "custom",
706
+ failureAlertAfter: "3",
707
+ failureAlertCooldownSeconds: "",
708
+ failureAlertChannel: "telegram",
709
+ failureAlertTo: "123456",
710
+ },
711
+ });
712
+
713
+ await addCronJob(state);
714
+
715
+ const updateCall = request.mock.calls.find(([method]) => method === "cron.update");
716
+ expect(updateCall).toBeDefined();
717
+ expect(updateCall?.[1]).toMatchObject({
718
+ id: "job-alert-no-cooldown",
719
+ patch: {
720
+ failureAlert: {
721
+ after: 3,
722
+ channel: "telegram",
723
+ to: "123456",
724
+ },
725
+ },
726
+ });
727
+ expect(
728
+ (updateCall?.[1] as { patch?: { failureAlert?: { cooldownMs?: number } } })?.patch
729
+ ?.failureAlert,
730
+ ).not.toHaveProperty("cooldownMs");
731
+ });
732
+
733
+ it("includes failureAlert=false when disabled per job", async () => {
734
+ const request = vi.fn(async (method: string, _payload?: unknown) => {
735
+ if (method === "cron.update") {
736
+ return { id: "job-no-alert" };
737
+ }
738
+ if (method === "cron.list") {
739
+ return { jobs: [{ id: "job-no-alert" }] };
740
+ }
741
+ if (method === "cron.status") {
742
+ return { enabled: true, jobs: 1, nextWakeAtMs: null };
743
+ }
744
+ return {};
745
+ });
746
+ const state = createState({
747
+ client: { request } as unknown as CronState["client"],
748
+ cronEditingJobId: "job-no-alert",
749
+ cronForm: {
750
+ ...DEFAULT_CRON_FORM,
751
+ name: "alert off",
752
+ payloadKind: "agentTurn",
753
+ payloadText: "run it",
754
+ failureAlertMode: "disabled",
755
+ },
756
+ });
757
+
758
+ await addCronJob(state);
759
+
760
+ const updateCall = request.mock.calls.find(([method]) => method === "cron.update");
761
+ expect(updateCall).toBeDefined();
762
+ expect(updateCall?.[1]).toMatchObject({
763
+ id: "job-no-alert",
764
+ patch: { failureAlert: false },
765
+ });
766
+ });
767
+
768
+ it("maps cron stagger, model, thinking, and best effort into form", () => {
769
+ const state = createState();
770
+ const job = {
771
+ id: "job-10",
772
+ name: "Advanced job",
773
+ enabled: true,
774
+ deleteAfterRun: true,
775
+ createdAtMs: 0,
776
+ updatedAtMs: 0,
777
+ schedule: { kind: "cron" as const, expr: "0 7 * * *", tz: "UTC", staggerMs: 60_000 },
778
+ sessionTarget: "isolated" as const,
779
+ wakeMode: "now" as const,
780
+ payload: {
781
+ kind: "agentTurn" as const,
782
+ message: "hi",
783
+ model: "opus",
784
+ thinking: "high",
785
+ },
786
+ delivery: { mode: "announce" as const, bestEffort: true },
787
+ state: {},
788
+ };
789
+ startCronEdit(state, job);
790
+
791
+ expect(state.cronForm.deleteAfterRun).toBe(true);
792
+ expect(state.cronForm.scheduleKind).toBe("cron");
793
+ expect(state.cronForm.scheduleExact).toBe(false);
794
+ expect(state.cronForm.staggerAmount).toBe("1");
795
+ expect(state.cronForm.staggerUnit).toBe("minutes");
796
+ expect(state.cronForm.payloadModel).toBe("opus");
797
+ expect(state.cronForm.payloadThinking).toBe("high");
798
+ expect(state.cronForm.deliveryBestEffort).toBe(true);
799
+ });
800
+
801
+ it("maps failureAlert overrides into form fields", () => {
802
+ const state = createState();
803
+ const job = {
804
+ id: "job-11",
805
+ name: "Failure alerts",
806
+ enabled: true,
807
+ createdAtMs: 0,
808
+ updatedAtMs: 0,
809
+ schedule: { kind: "every" as const, everyMs: 60_000 },
810
+ sessionTarget: "isolated" as const,
811
+ wakeMode: "next-heartbeat" as const,
812
+ payload: { kind: "agentTurn" as const, message: "hello" },
813
+ failureAlert: {
814
+ after: 4,
815
+ cooldownMs: 30_000,
816
+ channel: "telegram",
817
+ to: "999",
818
+ },
819
+ state: {},
820
+ };
821
+
822
+ startCronEdit(state, job);
823
+
824
+ expect(state.cronForm.failureAlertMode).toBe("custom");
825
+ expect(state.cronForm.failureAlertAfter).toBe("4");
826
+ expect(state.cronForm.failureAlertCooldownSeconds).toBe("30");
827
+ expect(state.cronForm.failureAlertChannel).toBe("telegram");
828
+ expect(state.cronForm.failureAlertTo).toBe("999");
829
+ expect(state.cronForm.failureAlertDeliveryMode).toBe("announce");
830
+ expect(state.cronForm.failureAlertAccountId).toBe("");
831
+ });
832
+
833
+ it("validates key cron form errors", () => {
834
+ const errors = validateCronForm({
835
+ ...DEFAULT_CRON_FORM,
836
+ name: "",
837
+ scheduleKind: "cron",
838
+ cronExpr: "",
839
+ payloadKind: "agentTurn",
840
+ payloadText: "",
841
+ timeoutSeconds: "0",
842
+ deliveryMode: "webhook",
843
+ deliveryTo: "ftp://bad",
844
+ });
845
+ expect(errors.name).toBe("cron.errors.nameRequired");
846
+ expect(errors.cronExpr).toBe("cron.errors.cronExprRequired");
847
+ expect(errors.payloadText).toBe("cron.errors.agentMessageRequired");
848
+ expect(errors.timeoutSeconds).toBe("cron.errors.timeoutInvalid");
849
+ expect(errors.deliveryTo).toBe("cron.errors.webhookUrlInvalid");
850
+ });
851
+
852
+ it("blocks add/update submit when validation errors exist", async () => {
853
+ const request = vi.fn(async () => ({}));
854
+ const state = createState({
855
+ client: { request } as unknown as CronState["client"],
856
+ cronForm: {
857
+ ...DEFAULT_CRON_FORM,
858
+ name: "",
859
+ payloadText: "",
860
+ },
861
+ });
862
+ await addCronJob(state);
863
+ expect(request).not.toHaveBeenCalled();
864
+ expect(state.cronFieldErrors.name).toBeDefined();
865
+ expect(state.cronFieldErrors.payloadText).toBeDefined();
866
+ });
867
+
868
+ it("canceling edit resets form to defaults and clears edit mode", () => {
869
+ const state = createState();
870
+ const job = {
871
+ id: "job-cancel",
872
+ name: "Editable",
873
+ enabled: true,
874
+ createdAtMs: 0,
875
+ updatedAtMs: 0,
876
+ schedule: { kind: "cron" as const, expr: "0 6 * * *" },
877
+ sessionTarget: "isolated" as const,
878
+ wakeMode: "now" as const,
879
+ payload: { kind: "agentTurn" as const, message: "run" },
880
+ delivery: { mode: "announce" as const, to: "123" },
881
+ state: {},
882
+ };
883
+ startCronEdit(state, job);
884
+ state.cronForm.name = "changed";
885
+ state.cronFieldErrors = { name: "Name is required." };
886
+
887
+ cancelCronEdit(state);
888
+
889
+ expect(state.cronEditingJobId).toBeNull();
890
+ expect(state.cronForm).toEqual({ ...DEFAULT_CRON_FORM });
891
+ expect(state.cronFieldErrors).toEqual(validateCronForm(DEFAULT_CRON_FORM));
892
+ });
893
+
894
+ it("cloning a job switches to create mode and applies copy naming", () => {
895
+ const state = createState({
896
+ cronJobs: [
897
+ {
898
+ id: "job-1",
899
+ name: "Daily ping",
900
+ enabled: true,
901
+ createdAtMs: 0,
902
+ updatedAtMs: 0,
903
+ schedule: { kind: "cron", expr: "0 9 * * *" },
904
+ sessionTarget: "main",
905
+ wakeMode: "next-heartbeat",
906
+ payload: { kind: "systemEvent", text: "ping" },
907
+ state: {},
908
+ },
909
+ ],
910
+ cronEditingJobId: "job-1",
911
+ });
912
+
913
+ const sourceJob = state.cronJobs[0];
914
+ expect(sourceJob).toBeDefined();
915
+ if (!sourceJob) {
916
+ return;
917
+ }
918
+ startCronClone(state, sourceJob);
919
+
920
+ expect(state.cronEditingJobId).toBeNull();
921
+ expect(state.cronRunsJobId).toBe("job-1");
922
+ expect(state.cronForm.name).toBe("Daily ping copy");
923
+ expect(state.cronForm.payloadText).toBe("ping");
924
+ });
925
+
926
+ it("submits cron.add after cloning", async () => {
927
+ const request = vi.fn(async (method: string, _payload?: unknown) => {
928
+ if (method === "cron.add") {
929
+ return { id: "job-new" };
930
+ }
931
+ if (method === "cron.list") {
932
+ return { jobs: [] };
933
+ }
934
+ if (method === "cron.status") {
935
+ return { enabled: true, jobs: 0, nextWakeAtMs: null };
936
+ }
937
+ return {};
938
+ });
939
+ const sourceJob = {
940
+ id: "job-1",
941
+ name: "Daily ping",
942
+ enabled: true,
943
+ createdAtMs: 0,
944
+ updatedAtMs: 0,
945
+ schedule: { kind: "cron" as const, expr: "0 9 * * *" },
946
+ sessionTarget: "main" as const,
947
+ wakeMode: "next-heartbeat" as const,
948
+ payload: { kind: "systemEvent" as const, text: "ping" },
949
+ state: {},
950
+ };
951
+ const state = createState({
952
+ client: { request } as unknown as CronState["client"],
953
+ cronJobs: [sourceJob],
954
+ cronEditingJobId: "job-1",
955
+ });
956
+
957
+ startCronClone(state, sourceJob);
958
+ await addCronJob(state);
959
+
960
+ const addCall = request.mock.calls.find(([method]) => method === "cron.add");
961
+ const updateCall = request.mock.calls.find(([method]) => method === "cron.update");
962
+ expect(addCall).toBeDefined();
963
+ expect(updateCall).toBeUndefined();
964
+ expect((addCall?.[1] as { name?: string } | undefined)?.name).toBe("Daily ping copy");
965
+ });
966
+
967
+ it("loads paged jobs with query/filter/sort params", async () => {
968
+ const request = vi.fn(async (method: string, payload?: unknown) => {
969
+ if (method === "cron.list") {
970
+ expect(payload).toMatchObject({
971
+ limit: 50,
972
+ offset: 0,
973
+ query: "daily",
974
+ enabled: "enabled",
975
+ sortBy: "updatedAtMs",
976
+ sortDir: "desc",
977
+ });
978
+ return {
979
+ jobs: [{ id: "job-1", name: "Daily", enabled: true }],
980
+ total: 1,
981
+ hasMore: false,
982
+ nextOffset: null,
983
+ };
984
+ }
985
+ return {};
986
+ });
987
+ const state = createState({
988
+ client: { request } as unknown as CronState["client"],
989
+ cronJobsQuery: "daily",
990
+ cronJobsEnabledFilter: "enabled",
991
+ cronJobsSortBy: "updatedAtMs",
992
+ cronJobsSortDir: "desc",
993
+ });
994
+
995
+ await loadCronJobsPage(state);
996
+
997
+ expect(state.cronJobs).toHaveLength(1);
998
+ expect(state.cronJobsTotal).toBe(1);
999
+ expect(state.cronJobsHasMore).toBe(false);
1000
+ });
1001
+
1002
+ it("loads and appends paged run history", async () => {
1003
+ const request = vi.fn(async (method: string, payload?: unknown) => {
1004
+ if (method !== "cron.runs") {
1005
+ return {};
1006
+ }
1007
+ const offset = (payload as { offset?: number } | undefined)?.offset ?? 0;
1008
+ if (offset === 0) {
1009
+ return {
1010
+ entries: [{ ts: 2, jobId: "job-1", status: "ok", summary: "newest" }],
1011
+ total: 2,
1012
+ hasMore: true,
1013
+ nextOffset: 1,
1014
+ };
1015
+ }
1016
+ return {
1017
+ entries: [{ ts: 1, jobId: "job-1", status: "ok", summary: "older" }],
1018
+ total: 2,
1019
+ hasMore: false,
1020
+ nextOffset: null,
1021
+ };
1022
+ });
1023
+ const state = createState({
1024
+ client: { request } as unknown as CronState["client"],
1025
+ });
1026
+
1027
+ await loadCronRuns(state, "job-1");
1028
+ expect(state.cronRuns).toHaveLength(1);
1029
+ expect(state.cronRunsHasMore).toBe(true);
1030
+
1031
+ await loadMoreCronRuns(state);
1032
+ expect(state.cronRuns).toHaveLength(2);
1033
+ expect(state.cronRuns[0]?.summary).toBe("newest");
1034
+ expect(state.cronRuns[1]?.summary).toBe("older");
1035
+ });
1036
+
1037
+ it("runs cron job in due mode when requested", async () => {
1038
+ const request = vi.fn(async (method: string, payload?: unknown) => {
1039
+ if (method === "cron.run") {
1040
+ expect(payload).toMatchObject({ id: "job-due", mode: "due" });
1041
+ return { ok: true };
1042
+ }
1043
+ if (method === "cron.runs") {
1044
+ return { entries: [], total: 0, hasMore: false, nextOffset: null };
1045
+ }
1046
+ return {};
1047
+ });
1048
+ const state = createState({
1049
+ client: { request } as unknown as CronState["client"],
1050
+ cronRunsScope: "job",
1051
+ cronRunsJobId: "job-due",
1052
+ });
1053
+ const job = {
1054
+ id: "job-due",
1055
+ name: "Due test",
1056
+ enabled: true,
1057
+ createdAtMs: 0,
1058
+ updatedAtMs: 0,
1059
+ schedule: { kind: "cron" as const, expr: "0 * * * *" },
1060
+ sessionTarget: "isolated" as const,
1061
+ wakeMode: "now" as const,
1062
+ payload: { kind: "agentTurn" as const, message: "run" },
1063
+ state: {},
1064
+ };
1065
+
1066
+ await runCronJob(state, job, "due");
1067
+
1068
+ expect(request).toHaveBeenCalledWith("cron.run", { id: "job-due", mode: "due" });
1069
+ });
1070
+ });