chainlesschain 0.163.5 → 0.163.6

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 (194) hide show
  1. package/completions/_cc +1 -1
  2. package/completions/cc.bash +1 -1
  3. package/completions/cc.fish +1 -1
  4. package/completions/cc.ps1 +1 -1
  5. package/package.json +1 -1
  6. package/src/assets/web-panel/assets/{AIOps-Cv2EE1Zy.js → AIOps-DDIUofA8.js} +1 -1
  7. package/src/assets/web-panel/assets/{ActionButton-CcpxMLuW.js → ActionButton-Bm-5xYRn.js} +1 -1
  8. package/src/assets/web-panel/assets/{Analytics-DE8mJoMI.js → Analytics-D5UH4ljI.js} +3 -3
  9. package/src/assets/web-panel/assets/{AppLayout-DKn9dk9L.js → AppLayout-D_ifjOOP.js} +4 -4
  10. package/src/assets/web-panel/assets/{Artifacts-Cp3mG3Py.js → Artifacts-BY1mXdC7.js} +1 -1
  11. package/src/assets/web-panel/assets/{Audit-zvsQfF2C.js → Audit-Bujr4Ml3.js} +1 -1
  12. package/src/assets/web-panel/assets/{BackgroundAgents-Bi85SsjN.js → BackgroundAgents-sioKSTcC.js} +1 -1
  13. package/src/assets/web-panel/assets/{Backup-Cw47Dj6s.js → Backup-DDOAK8RK.js} +1 -1
  14. package/src/assets/web-panel/assets/{BaseInput-C5Ip2afH.js → BaseInput-CtCsVVke.js} +1 -1
  15. package/src/assets/web-panel/assets/{Chat-C0iAFkJv.js → Chat-DpNj0vnd.js} +5 -5
  16. package/src/assets/web-panel/assets/{ChatBubbleRenderer-BV9Wv7wO.js → ChatBubbleRenderer-CZta3Ftr.js} +1 -1
  17. package/src/assets/web-panel/assets/{Checkbox-3KZZCiAF.js → Checkbox-DoHVNSXq.js} +1 -1
  18. package/src/assets/web-panel/assets/{Codegen-C8OMbBAz.js → Codegen-DlVl7Qmv.js} +1 -1
  19. package/src/assets/web-panel/assets/{Col-B-MB84LJ.js → Col-GIqoPYty.js} +1 -1
  20. package/src/assets/web-panel/assets/{Community-DpR7Dxao.js → Community-CIAPEVji.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compact-CyDtz0OS.js → Compact-BN7JiI_h.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compliance-5tiRa2An.js → Compliance-CqUWCM9B.js} +1 -1
  23. package/src/assets/web-panel/assets/{Cowork-DnQaKZ5x.js → Cowork-A4iPp7A4.js} +4 -4
  24. package/src/assets/web-panel/assets/{Cron-BaWfoEDf.js → Cron-B2zeXXWb.js} +2 -2
  25. package/src/assets/web-panel/assets/{Crosschain-Bu0HoHhb.js → Crosschain-BR2xmWr8.js} +1 -1
  26. package/src/assets/web-panel/assets/{DID-31zk8upX.js → DID-BzFDjrIv.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dashboard-BmVne-e2.js → Dashboard-DKtDo3yA.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dropdown-B4PkoOWd.js → Dropdown-RNx43_YG.js} +1 -1
  29. package/src/assets/web-panel/assets/{EmailListRenderer-BZ6tyFtI.js → EmailListRenderer-SwMt4Rcy.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Osozn4Ds.js → FamilyGuardDashboard-CsYebZ4X.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-DaH8TOAP.js → Federation-BmRKvBTy.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-tbU0uwis.js → FormItemContext-CVxGTA-0.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-Df8LQBrL.js → GenericCardRenderer-x2iXpyB7.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-DsMB6-14.js → Git-DfqbkiOP.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-BnH0dZzd.js → Governance-RuHyyj5O.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-DhnN1N_P.js → Inference-Bn7uZ7rI.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-DnNshAgn.js → KnowledgeGraph-DlP7XefZ.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-BmBFX3_N.js → Logs-BYNdOiCd.js} +2 -2
  39. package/src/assets/web-panel/assets/{MarkdownRenderer-DCslfQU3.js → MarkdownRenderer-aMtgN-R6.js} +1 -1
  40. package/src/assets/web-panel/assets/{Marketplace-CPEgWDc3.js → Marketplace-JQYmDqM8.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-C36qjgF9.js → McpTools-Cyx4yZ-9.js} +5 -5
  42. package/src/assets/web-panel/assets/{Memory-tNHvrgqN.js → Memory-VDuGZTN2.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-Xaf6cKjc.js → MobileBridge-_yKo97gi.js} +1 -1
  44. package/src/assets/web-panel/assets/{MobileProjects-D2E5TTeO.js → MobileProjects-CXRXW3q7.js} +1 -1
  45. package/src/assets/web-panel/assets/{Mtc-D4AJ2dwV.js → Mtc-acTGtfLH.js} +5 -5
  46. package/src/assets/web-panel/assets/{MtcAudit-Df-gtn2_.js → MtcAudit-ClpjXfkY.js} +4 -4
  47. package/src/assets/web-panel/assets/{Multisig-DTCHLWld.js → Multisig-_6Tn3o96.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-DruZ_1Jb.js → NLProgramming-CK7pbZzP.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-Dx3pdGDs.js → Notes-Czy6V_P8.js} +4 -4
  50. package/src/assets/web-panel/assets/{NotificationSettings-Bi3L69oL.js → NotificationSettings-Cqx-UVv3.js} +1 -1
  51. package/src/assets/web-panel/assets/{OrderTableRenderer-BHY33m7C.js → OrderTableRenderer-C69WDxGy.js} +1 -1
  52. package/src/assets/web-panel/assets/{Organization-BG2ZVEV-.js → Organization-DFnA9vLQ.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-Viwtg4Mn.js → Overflow-ReZkk2yT.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-Clm_yc1Z.js → P2P-DzjSI0aI.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-NKmmDiOw.js → PdhVaultBrowser-DKTf5xIP.js} +5 -5
  56. package/src/assets/web-panel/assets/{Permissions-DIL8u9Nk.js → Permissions-DhQ5klhR.js} +4 -4
  57. package/src/assets/web-panel/assets/{PersonalDataHub-B-jM4wt-.js → PersonalDataHub-CUL1iHrW.js} +4 -4
  58. package/src/assets/web-panel/assets/{Pipeline-627I__H5.js → Pipeline-DU88iu4N.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-2DN2qrkW.js → Privacy-Bw-sYfyK.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-CN4wC0sF.js → ProjectInit-Bq3Q5y0z.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-K35vsh5H.js → ProjectSettings-C8spzpho.js} +2 -2
  62. package/src/assets/web-panel/assets/Projects-Dd-xuPIc.js +1 -0
  63. package/src/assets/web-panel/assets/{Providers-MckaQUhQ.js → Providers-Bf4eTP9W.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-BS3mtA_O.js → QrScannerModal-Dme9BDU1.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-BHp_nofL.js → QuickAsk-CIXfv1rE.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-DlmnPuZj.js → Recommend-CIxvdeZ-.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-tsyFbvpy.js → RemoteSession-C6k7MExI.js} +2 -2
  68. package/src/assets/web-panel/assets/{Reputation-Ba1iWv2D.js → Reputation-CISDz2bl.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-NnlGC--f.js → Row-CdnDxXyf.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-CjThSIRv.js → RssFeed-C-QpRHsl.js} +3 -3
  71. package/src/assets/web-panel/assets/{Search-CojUyC5x.js → Search-DBjaGzSn.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-c-sxeJEc.js → Security-CQKukOeE.js} +3 -3
  73. package/src/assets/web-panel/assets/{Services-CtcKEEmF.js → Services-DZz86jWb.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-CIBBXflN.js → Skeleton-jb8RMK0d.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-D1XOOK9n.js → Skills-CdY76fZG.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-hRfEp5Sn.js → Sla-C_CSiZX9.js} +1 -1
  77. package/src/assets/web-panel/assets/SpeechSettings-C7tT9iad.js +1 -0
  78. package/src/assets/web-panel/assets/{SyncSettings-BJsSFGYy.js → SyncSettings-qvimGO0O.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-CfSZ8A1L.js → Tasks-Dng5LPJC.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-idDf47W-.js → Templates-Bwqq5Wll.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-C-UsX6w6.js → Tenant-DNWuwP7Z.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-BgJtX5n1.js → Terminal-B-6Othpp.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-DP0BLKTl.js → TimelineRenderer-DKpKZfzx.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-LwCzqETp.js → Tokens-DzfGMd2J.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-D_9tVch0.js → Trigger-BXwUi74J.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-BH2UlEWh.js → Trust-BNzUq4ui.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-CIpD0nYb.js → UkeySign-BYugxww4.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-9ShhYxK_.js → VideoEditing-DdauhtSJ.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-C7afGNIG.js → Wallet-CSC9bCe_.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-bUKn6i5R.js → WebAuthn-Q8AN3bTO.js} +5 -5
  91. package/src/assets/web-panel/assets/{WorkflowEditor-CxgQaGyp.js → WorkflowEditor-d-LBGCuA.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-DMVkmJwx.js → chat-BLVDCgsZ.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-CDzQh5nS.js → colors-BZlPSEHl.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-C51kEF4D.js → compact-item-IWseP6R6.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-BTk9bXBn.js → createContext-DnVRV3Ao.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-Jzu62CLC.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-CMje33rb.js → hasIn-DLQZA_Wn.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BBz9LIMS.js → index-1jeAudHd.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-hg_emBsi.js → index-BEu2lMBy.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DRG-aqOs.js → index-BOZJBTWZ.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CDLkNkLj.js → index-BSjilZ9L.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CvSeVzqe.js → index-BZs5bBnk.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DpcrFxMI.js → index-B_N4-Gq9.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-D9rC-fGW.js → index-Bbj2NS_I.js} +3 -3
  105. package/src/assets/web-panel/assets/{index-BVePe_Rf.js → index-BhA3QEni.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BzCkNylB.js → index-Bxp7uDD8.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BAfY7m7i.js → index-C1ifZtXo.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BTlsE8Df.js → index-C2FqfMvU.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DqU9njjT.js → index-C5dgnilC.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BtlEqHLP.js → index-CINJQEj6.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BeEaAEIp.js → index-COev0W2g.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CCLj9hcD.js → index-CT9vVN-X.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-h31MwrME.js → index-CZDekj7T.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Ld_4mFmC.js → index-CarMTIyQ.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CwGPwjUu.js → index-CbQNL0ue.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-C5IWTMVl.js → index-CpBaJ3TD.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BW4F5YDF.js → index-DB0MJgF_.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CypsfcEq.js → index-DOoQpuQY.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CfC-piWZ.js → index-DTbxp4UH.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BCRvG9lq.js → index-DUq0c7gq.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-0Auljb10.js → index-DZpYeqrh.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-C4rQCS8T.js → index-D_LQodeJ.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CtM8OT_7.js → index-DalA0TKC.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-C1oZQmAA.js → index-DjWnONdy.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DRLQyp2A.js → index-Dkwsi1Kn.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-togc_uYn.js → index-DocEhinm.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-x53Vf7la.js → index-DpVrK6fr.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DgivOKHA.js → index-Dy0R5ZED.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-B0-zMtKA.js → index-M7wxfAbm.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BXQBPLyE.js → index-N_GoWZX4.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-C1oZReaY.js → index-YSw_gxlZ.js} +1 -1
  132. package/src/assets/web-panel/assets/index-Zb2MeDSy.js +1 -0
  133. package/src/assets/web-panel/assets/index-fikqml4k.js +1 -0
  134. package/src/assets/web-panel/assets/{index-Cd0hmPxD.js → index-gt8bNefW.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-OS-vfbTA.js → index-hwVDeoC0.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-DJfnCuMq.js → index-jWb70ZgZ.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-vrA8K-ac.js → initDefaultProps-Co67Kqmb.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-ByEzwIAl.js → motion-BMOxQSF_.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-Ck4AWjw-.js → move-C5fWDw-N.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-6p0l9SrB.js → mtc-parser-BJevpL0e.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-B1CUPQ6R.js → omit-BCzNU8s6.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-B1STg1iW.js → pickAttrs-CybWvDPk.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-DTTllkBV.js → placementArrow-BMWS9HnW.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-AIrrCfBb.js → responsiveObserve-BoL2UnGY.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-C_fPMjdR.js → slide-DWNfXraU.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-D5ow2yIP.js → statusUtils-BquTM3tX.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-Dy3hEA--.js → styleChecker-BEkCleim.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-WfzcfSh4.js → useFlexGapSupport-Oecnq7EA.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-CKSejfas.js → useFs-CkDffJ4F.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-od3zpPBN.js → usePersonalDataHub-qbvKXesB.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-DRD_2DFA.js → vnode-C6v8n6mA.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-DmNrEtgf.js → zoom-4CTeFILV.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/command-help-index.json +3 -3
  155. package/src/command-manifest.json +1 -1
  156. package/src/commands/agenda.js +76 -4
  157. package/src/commands/automation.js +462 -3
  158. package/src/commands/background-session.js +102 -1
  159. package/src/commands/cowork.js +6 -1
  160. package/src/commands/loop.js +167 -36
  161. package/src/commands/routine.js +11 -4
  162. package/src/commands/scheduler-daemon.js +450 -0
  163. package/src/data/changelog.json +15 -2
  164. package/src/lib/agent-schedule-store.js +323 -43
  165. package/src/lib/automation-center-routines.js +363 -0
  166. package/src/lib/automation-center.js +435 -0
  167. package/src/lib/automation-engine.js +74 -19
  168. package/src/lib/automation-execution-authority.js +736 -0
  169. package/src/lib/background-agent-supervisor.js +78 -0
  170. package/src/lib/background-needs-input-incident.js +221 -0
  171. package/src/lib/cowork-cron.js +316 -18
  172. package/src/lib/cowork-observe.js +5 -1
  173. package/src/lib/loop.js +37 -2
  174. package/src/lib/permission-engine.js +14 -7
  175. package/src/lib/public-ide-capability-manifest.js +2 -0
  176. package/src/lib/routine-store.js +166 -65
  177. package/src/lib/scheduler-kernel/agenda-adapter.js +297 -27
  178. package/src/lib/scheduler-kernel/authority-resolver.js +163 -0
  179. package/src/lib/scheduler-kernel/automation-adapter.js +592 -0
  180. package/src/lib/scheduler-kernel/automation-event-adapter.js +721 -0
  181. package/src/lib/scheduler-kernel/contract.js +11 -3
  182. package/src/lib/scheduler-kernel/cowork-cron-adapter.js +58 -12
  183. package/src/lib/scheduler-kernel/loop-adapter.js +378 -0
  184. package/src/lib/scheduler-kernel/routine-adapter.js +303 -8
  185. package/src/lib/scheduler-kernel/service.js +328 -0
  186. package/src/lib/scheduler-kernel/store.js +828 -96
  187. package/src/runtime/agent-core.js +2 -1
  188. package/src/runtime/coding-agent-contract-shared.cjs +5 -0
  189. package/src/workers/background-agent-worker.js +91 -28
  190. package/src/assets/web-panel/assets/Projects-B_wQadhX.js +0 -1
  191. package/src/assets/web-panel/assets/SpeechSettings-Dexb1f_6.js +0 -1
  192. package/src/assets/web-panel/assets/devWarning-CdwPwYj8.js +0 -1
  193. package/src/assets/web-panel/assets/index-BEeOdfEe.js +0 -1
  194. package/src/assets/web-panel/assets/index-BTe0TOu6.js +0 -1
@@ -27,6 +27,11 @@ import path from "node:path";
27
27
  import os from "node:os";
28
28
  import { randomBytes } from "node:crypto";
29
29
  import { parseCron, nextCronTime } from "./agent-schedule-store.js";
30
+ import {
31
+ readSecurityStore,
32
+ writeSecurityStore,
33
+ } from "./durable-security-store.js";
34
+ import { withFileLock } from "./with-file-lock.js";
30
35
 
31
36
  export const ROUTINE_TRIGGER_KINDS = Object.freeze([
32
37
  "cron",
@@ -45,11 +50,75 @@ function shortId() {
45
50
 
46
51
  function parseWhen(value) {
47
52
  if (value == null) return NaN;
53
+ if (typeof value === "string" && value.trim() === "") return NaN;
54
+ if (typeof value !== "string" && typeof value !== "number") return NaN;
48
55
  const n = Number(value);
49
56
  if (Number.isFinite(n)) return n;
50
57
  return Date.parse(String(value));
51
58
  }
52
59
 
60
+ function boundedDefinitionText(value, field, maximum) {
61
+ const text = String(value || "").trim();
62
+ if (!text) throw new Error(`routine needs a ${field}`);
63
+ if (Buffer.byteLength(text, "utf8") > maximum) {
64
+ throw new Error(`routine ${field} exceeds ${maximum} UTF-8 bytes`);
65
+ }
66
+ return text;
67
+ }
68
+
69
+ export function normalizeRoutineDefinition(def = {}) {
70
+ const name = boundedDefinitionText(def.name, "name", 512);
71
+ const prompt = boundedDefinitionText(def.prompt, "prompt", 64 * 1024);
72
+ const raw = def.trigger;
73
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
74
+ throw new Error("routine trigger is required");
75
+ }
76
+ if (!ROUTINE_TRIGGER_KINDS.includes(raw.kind)) {
77
+ throw new Error(
78
+ `routine trigger kind must be one of: ${ROUTINE_TRIGGER_KINDS.join(", ")}`,
79
+ );
80
+ }
81
+ let trigger;
82
+ if (raw.kind === "cron") {
83
+ const cron = String(raw.cron || "").trim();
84
+ if (!parseCron(cron)) throw new Error(`invalid cron expression: ${cron}`);
85
+ trigger = { kind: "cron", cron };
86
+ } else if (raw.kind === "once") {
87
+ const at = parseWhen(raw.at);
88
+ if (!Number.isFinite(at) || !Number.isSafeInteger(at) || at < 0) {
89
+ throw new Error(`invalid --at time: ${raw.at}`);
90
+ }
91
+ trigger = { kind: "once", at };
92
+ } else if (raw.kind === "github") {
93
+ const repo = String(raw.repo || "").trim();
94
+ const parts = repo.split("/");
95
+ if (
96
+ !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(repo) ||
97
+ parts.some((part) => part === "." || part === "..")
98
+ ) {
99
+ throw new Error("github trigger needs --repo <owner/name>");
100
+ }
101
+ const events = Array.isArray(raw.events)
102
+ ? [...new Set(raw.events.map((value) => String(value || "").trim()))]
103
+ .filter(Boolean)
104
+ .sort()
105
+ : [];
106
+ if (
107
+ events.length > 64 ||
108
+ events.some(
109
+ (event) =>
110
+ event.length > 128 || !/^[A-Za-z][A-Za-z0-9_.-]*$/u.test(event),
111
+ )
112
+ ) {
113
+ throw new Error("github trigger events are invalid");
114
+ }
115
+ trigger = { kind: "github", repo, events };
116
+ } else {
117
+ trigger = { kind: "webhook" };
118
+ }
119
+ return { name, prompt, trigger };
120
+ }
121
+
53
122
  export class RoutineStore {
54
123
  constructor({ dir = null, now = () => Date.now() } = {}) {
55
124
  this.dir = dir || routinesDir();
@@ -70,68 +139,58 @@ export class RoutineStore {
70
139
  }
71
140
 
72
141
  _readRoutines() {
73
- try {
74
- const parsed = JSON.parse(fs.readFileSync(this._routinesFile(), "utf-8"));
75
- return parsed && typeof parsed === "object" ? parsed : {};
76
- } catch {
77
- return {};
78
- }
142
+ return readSecurityStore(this._routinesFile(), "routine definitions");
79
143
  }
80
144
  _writeRoutines(map) {
145
+ writeSecurityStore(this._routinesFile(), "routine definitions", map);
146
+ }
147
+
148
+ _withDefinitionLock(callback) {
81
149
  this._ensureDir();
82
- fs.writeFileSync(
150
+ return withFileLock(
83
151
  this._routinesFile(),
84
- JSON.stringify(map, null, 2),
85
- "utf-8",
152
+ () => callback(this._readRoutines()),
153
+ { failIfUnavailable: true },
86
154
  );
87
155
  }
88
156
 
89
- /**
90
- * @param {object} def { name, prompt, trigger: {kind, cron?, at?, repo?, events?} }
91
- */
92
- create(def = {}) {
93
- const name = String(def.name || "").trim();
94
- const prompt = String(def.prompt || "").trim();
95
- const trigger = def.trigger || {};
96
- if (!name) throw new Error("routine needs a name");
97
- if (!prompt) throw new Error("routine needs a prompt");
98
- if (!ROUTINE_TRIGGER_KINDS.includes(trigger.kind)) {
99
- throw new Error(
100
- `routine trigger kind must be one of: ${ROUTINE_TRIGGER_KINDS.join(", ")}`,
101
- );
102
- }
103
- if (trigger.kind === "cron") {
104
- if (!parseCron(trigger.cron || "")) {
105
- throw new Error(`invalid cron expression: ${trigger.cron}`);
106
- }
107
- }
108
- if (trigger.kind === "once") {
109
- const at = parseWhen(trigger.at);
110
- if (!Number.isFinite(at)) {
111
- throw new Error(`invalid --at time: ${trigger.at}`);
112
- }
113
- trigger.at = at;
114
- }
115
- if (trigger.kind === "github" && !trigger.repo) {
116
- throw new Error("github trigger needs --repo <owner/name>");
157
+ _resolveFromMap(map, id) {
158
+ if (map[id]) return map[id];
159
+ const matches = Object.values(map).filter(
160
+ (routine) => routine.id.startsWith(id) || routine.name === id,
161
+ );
162
+ if (matches.length === 1) return matches[0];
163
+ if (matches.length > 1) {
164
+ throw new Error(`routine "${id}" is ambiguous (${matches.length} match)`);
117
165
  }
166
+ return null;
167
+ }
118
168
 
169
+ _createInMap(map, def) {
170
+ const definition = normalizeRoutineDefinition(def);
119
171
  const routine = {
120
172
  id: shortId(),
121
- name,
122
- prompt,
123
- trigger,
173
+ ...definition,
124
174
  enabled: true,
125
175
  createdAt: this._now(),
126
176
  lastFiredAt: null,
127
177
  lastSeenGithubEventId: null,
128
178
  };
129
- const map = this._readRoutines();
130
179
  map[routine.id] = routine;
131
- this._writeRoutines(map);
132
180
  return routine;
133
181
  }
134
182
 
183
+ /**
184
+ * @param {object} def { name, prompt, trigger: {kind, cron?, at?, repo?, events?} }
185
+ */
186
+ create(def = {}) {
187
+ return this._withDefinitionLock((map) => {
188
+ const routine = this._createInMap(map, def);
189
+ this._writeRoutines(map);
190
+ return routine;
191
+ });
192
+ }
193
+
135
194
  list() {
136
195
  return Object.values(this._readRoutines()).sort(
137
196
  (a, b) => (b.createdAt || 0) - (a.createdAt || 0),
@@ -140,26 +199,26 @@ export class RoutineStore {
140
199
 
141
200
  get(id) {
142
201
  const map = this._readRoutines();
143
- if (map[id]) return map[id];
144
- // prefix / name match for CLI ergonomics
145
- const matches = Object.values(map).filter(
146
- (r) => r.id.startsWith(id) || r.name === id,
147
- );
148
- if (matches.length === 1) return matches[0];
149
- if (matches.length > 1) {
150
- throw new Error(`routine "${id}" is ambiguous (${matches.length} match)`);
151
- }
152
- return null;
202
+ return this._resolveFromMap(map, id);
153
203
  }
154
204
 
155
205
  update(id, patch) {
156
- const map = this._readRoutines();
157
- const routine = this.get(id);
158
- if (!routine) throw new Error(`routine not found: ${id}`);
159
- const next = { ...routine, ...patch, id: routine.id };
160
- map[routine.id] = next;
161
- this._writeRoutines(map);
162
- return next;
206
+ return this._withDefinitionLock((map) => {
207
+ const routine = this._resolveFromMap(map, id);
208
+ if (!routine) throw new Error(`routine not found: ${id}`);
209
+ const next = { ...routine, ...patch, id: routine.id };
210
+ map[routine.id] = next;
211
+ this._writeRoutines(map);
212
+ return next;
213
+ });
214
+ }
215
+
216
+ updateDefinition(id, definition) {
217
+ const normalized = normalizeRoutineDefinition(definition);
218
+ return this.update(id, {
219
+ ...normalized,
220
+ lastSeenGithubEventId: null,
221
+ });
163
222
  }
164
223
 
165
224
  setEnabled(id, enabled) {
@@ -167,12 +226,54 @@ export class RoutineStore {
167
226
  }
168
227
 
169
228
  remove(id) {
170
- const map = this._readRoutines();
171
- const routine = this.get(id);
172
- if (!routine) throw new Error(`routine not found: ${id}`);
173
- delete map[routine.id];
174
- this._writeRoutines(map);
175
- return routine;
229
+ return this._withDefinitionLock((map) => {
230
+ const routine = this._resolveFromMap(map, id);
231
+ if (!routine) throw new Error(`routine not found: ${id}`);
232
+ delete map[routine.id];
233
+ this._writeRoutines(map);
234
+ return routine;
235
+ });
236
+ }
237
+
238
+ createIfRevision(expectedRevision, revisionOfMap, definition) {
239
+ return this._withDefinitionLock((map) => {
240
+ if (revisionOfMap(map) !== expectedRevision) {
241
+ const error = new Error("routine collection changed before create");
242
+ error.code = "ROUTINE_REVISION_CONFLICT";
243
+ throw error;
244
+ }
245
+ const routine = this._createInMap(map, definition);
246
+ this._writeRoutines(map);
247
+ return routine;
248
+ });
249
+ }
250
+
251
+ readIfRevision(id, expectedRevision, revisionOfRoutine) {
252
+ return this._withDefinitionLock((map) => {
253
+ const routine = map[id];
254
+ if (!routine) throw new Error(`routine not found: ${id}`);
255
+ if (revisionOfRoutine(routine) !== expectedRevision) {
256
+ const error = new Error(`routine changed before action: ${id}`);
257
+ error.code = "ROUTINE_REVISION_CONFLICT";
258
+ throw error;
259
+ }
260
+ return structuredClone(routine);
261
+ });
262
+ }
263
+
264
+ mutateIfRevision(id, expectedRevision, revisionOfRoutine, mutation) {
265
+ return this._withDefinitionLock((map) => {
266
+ const routine = map[id];
267
+ if (!routine) throw new Error(`routine not found: ${id}`);
268
+ if (revisionOfRoutine(routine) !== expectedRevision) {
269
+ const error = new Error(`routine changed before mutation: ${id}`);
270
+ error.code = "ROUTINE_REVISION_CONFLICT";
271
+ throw error;
272
+ }
273
+ const result = mutation(routine, map);
274
+ this._writeRoutines(map);
275
+ return result;
276
+ });
176
277
  }
177
278
 
178
279
  /** Routines the driver should fire now (cron due / once reached). */