chainlesschain 0.162.155 → 0.162.157

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/README.md +1 -1
  2. package/package.json +2 -2
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-Cth_zZeA.js → AIOps-CfMP2Oud.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-zfwA3Oxn.js → ActionButton-Zu0N8BQL.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-FeKc4aU2.js → Analytics-DZTwj5T_.js} +3 -3
  7. package/src/assets/web-panel/assets/AppLayout-CVFJh47v.js +9 -0
  8. package/src/assets/web-panel/assets/Artifacts-BbN5V7au.js +1 -0
  9. package/src/assets/web-panel/assets/Artifacts-CRkepl4t.css +1 -0
  10. package/src/assets/web-panel/assets/{Audit-C2gRq5Qi.js → Audit-BMfiLj7T.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-TeTIFiW-.js → BackgroundAgents-BslPCdGF.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-CLJk3k6i.js → Backup-CQ_fMC1r.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-5ms537NY.js → BaseInput-CsFsARiC.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-4Nr5U9PJ.js → Chat-CwfBobco.js} +6 -6
  15. package/src/assets/web-panel/assets/ChatBubbleRenderer-K6Nf3xeN.js +1 -0
  16. package/src/assets/web-panel/assets/{Checkbox-B2Bca5ev.js → Checkbox-DkSyjjPp.js} +1 -1
  17. package/src/assets/web-panel/assets/{Codegen-D6Uysdbv.js → Codegen-B7Fx3zZ6.js} +1 -1
  18. package/src/assets/web-panel/assets/{Col-B3XQ8xc7.js → Col-Dzfqn8Ep.js} +1 -1
  19. package/src/assets/web-panel/assets/{Community-CqwKtoPz.js → Community-DhHPdzMM.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compact-BZZINTus.js → Compact-Ct18_8y7.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compliance-Dr3BXkWA.js → Compliance-CY64IqBi.js} +1 -1
  22. package/src/assets/web-panel/assets/{Cowork-rx_x9Wqc.js → Cowork-BYSmWlUl.js} +3 -3
  23. package/src/assets/web-panel/assets/{Cron-BpZr7rHc.js → Cron-_4xtP3Ug.js} +2 -2
  24. package/src/assets/web-panel/assets/{Crosschain-CeV5EYVg.js → Crosschain-B4qY-Ycr.js} +1 -1
  25. package/src/assets/web-panel/assets/{DID-fe2Qaowz.js → DID-CkCzWkx1.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dashboard-Fh_EdWVa.js → Dashboard-C1scT7Ke.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dropdown-N1PIwWYf.js → Dropdown-DBnQ2EWd.js} +1 -1
  28. package/src/assets/web-panel/assets/{EmailListRenderer-B6vK93Ed.js → EmailListRenderer-C9JhOTq0.js} +1 -1
  29. package/src/assets/web-panel/assets/{FamilyGuardDashboard-e3iUZhAg.js → FamilyGuardDashboard-CWXv5RUv.js} +1 -1
  30. package/src/assets/web-panel/assets/{Federation-uInc9YcN.js → Federation-fYdcbz9W.js} +1 -1
  31. package/src/assets/web-panel/assets/{FormItemContext-DmQZSX0z.js → FormItemContext-CJqOT3Yj.js} +1 -1
  32. package/src/assets/web-panel/assets/{GenericCardRenderer-CV_Z8OAo.js → GenericCardRenderer-DGCsHc5K.js} +1 -1
  33. package/src/assets/web-panel/assets/{Git-jXAhNN8d.js → Git-EH2K-H0Q.js} +2 -2
  34. package/src/assets/web-panel/assets/{Governance-Blw1esBB.js → Governance-DJn1X2pR.js} +1 -1
  35. package/src/assets/web-panel/assets/Inference-Dch1E8qd.js +1 -0
  36. package/src/assets/web-panel/assets/{KnowledgeGraph-5xrglMTK.js → KnowledgeGraph-QIur8j8-.js} +1 -1
  37. package/src/assets/web-panel/assets/{Logs-BGIG9798.js → Logs-DUgHldPg.js} +2 -2
  38. package/src/assets/web-panel/assets/Marketplace-DafrSfvd.js +1 -0
  39. package/src/assets/web-panel/assets/{McpTools-7U8tEhwK.js → McpTools-D2xRbfJx.js} +4 -4
  40. package/src/assets/web-panel/assets/{Memory-BrvboPLE.js → Memory-C6RbVHAo.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-nzcd7d1H.js → MobileBridge-BPtVBKqn.js} +1 -1
  42. package/src/assets/web-panel/assets/{MobileProjects-DETVNKa7.js → MobileProjects-Cuw832cr.js} +1 -1
  43. package/src/assets/web-panel/assets/{Mtc-SZcCYec3.js → Mtc-D6O9fWFa.js} +4 -4
  44. package/src/assets/web-panel/assets/{MtcAudit-Cx_d3N17.js → MtcAudit-CWZcXIH1.js} +5 -5
  45. package/src/assets/web-panel/assets/{Multisig-DtcmSUo-.js → Multisig-DhIBxTFd.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-Dgx2QHl-.js → NLProgramming-wkBFS33e.js} +1 -1
  47. package/src/assets/web-panel/assets/{Notes-oHTGEHoJ.js → Notes-B09FxncA.js} +3 -3
  48. package/src/assets/web-panel/assets/{NotificationSettings-qKD-IlOl.js → NotificationSettings-oQwtH6-9.js} +1 -1
  49. package/src/assets/web-panel/assets/{OrderTableRenderer-CiU4Sy1J.js → OrderTableRenderer-iFKeNf6Y.js} +1 -1
  50. package/src/assets/web-panel/assets/{Organization-BBNXnltP.js → Organization-CsF-NIjz.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-Df2fDbvT.js → Overflow-bLxv9Dqi.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-DUHj6zjO.js → P2P-DVwQtm4q.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-B2nWgdoV.js → PdhVaultBrowser-Bgic3QKm.js} +3 -3
  54. package/src/assets/web-panel/assets/{Permissions-CiW0afny.js → Permissions-as80-9ep.js} +4 -4
  55. package/src/assets/web-panel/assets/{PersonalDataHub-cc1mHU_D.js → PersonalDataHub-BGmUw36a.js} +2 -2
  56. package/src/assets/web-panel/assets/{Pipeline-B93zTapU.js → Pipeline-BM96zBxG.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-Db1jXWfd.js → Privacy-BBOzBSWe.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-DFoZ1h9U.js → ProjectInit-GHX41V28.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-BkkUnmYs.js → ProjectSettings-DraqzxKD.js} +2 -2
  60. package/src/assets/web-panel/assets/{Projects-Xa779XLC.js → Projects-BzzdUml4.js} +1 -1
  61. package/src/assets/web-panel/assets/{Providers-DOF01IEN.js → Providers-CSeYNAtW.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-TohRsKd0.js → QrScannerModal-DlzBCdXh.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-DjLcr1ZU.js → QuickAsk-p8iB7od_.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend--Ism6bMx.js → Recommend-DjOVXacd.js} +1 -1
  65. package/src/assets/web-panel/assets/{RemoteSession-B8CL3YHM.js → RemoteSession-CIGk7gk-.js} +2 -2
  66. package/src/assets/web-panel/assets/Reputation-CjT3M0Di.js +1 -0
  67. package/src/assets/web-panel/assets/{Row-BioxZBBH.js → Row-tw8m6T0c.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-B2LipwH3.js → RssFeed-B47KBx_f.js} +2 -2
  69. package/src/assets/web-panel/assets/Search-D2sx1PBe.js +1 -0
  70. package/src/assets/web-panel/assets/{Security-TibHuJWP.js → Security-JmtL8g7D.js} +3 -3
  71. package/src/assets/web-panel/assets/{Services-DtkuWseW.js → Services-BxrNqjin.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-BIcPQae6.js → Skeleton-udk8L18t.js} +1 -1
  73. package/src/assets/web-panel/assets/Skills-DooLT9of.js +1 -0
  74. package/src/assets/web-panel/assets/{Sla-uUSU185m.js → Sla-ITVZE1xI.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-B7Bgc9mk.js → SpeechSettings-DCkfqY1n.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-K-XrJ2s9.js → SyncSettings-CkNlgFjM.js} +2 -2
  77. package/src/assets/web-panel/assets/Tasks-BMAoyCM_.js +1 -0
  78. package/src/assets/web-panel/assets/{Templates-CaFnyGDK.js → Templates-BnrFTlrz.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-C2X0PtEC.js → Tenant-BjNsy3-i.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-CKU5yVsT.js → Terminal-C0gxLL7V.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-8trZeYDC.js → TimelineRenderer-CUtj_KHA.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-BUEEe9US.js → Tokens-DOg61zlq.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-COU97I7Y.js → Trigger-D4ldCewj.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-DNWQChaP.js → Trust-3lk_Zd8j.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-DWX_VtCp.js → UkeySign-kWhFR6Li.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-D9eIDhsp.js → VideoEditing-D3GOFVWg.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-BOnCCgIL.js → Wallet-DzrZfBuh.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-DN4dD-W8.js → WebAuthn-CyVJVp_g.js} +5 -5
  89. package/src/assets/web-panel/assets/{WorkflowEditor-ClvDb_jX.js → WorkflowEditor-i_b0wi9b.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-DIxQ_Eeb.js → chat-DwPCK8I7.js} +1 -1
  91. package/src/assets/web-panel/assets/{colors-BOX3WKcP.js → colors-D0vaVqZU.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-Bny82VDi.js → compact-item-Don7nGW2.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-Cnoy3enS.js → createContext-D84R5qy2.js} +1 -1
  94. package/src/assets/web-panel/assets/devWarning-BBwWehQ7.js +1 -0
  95. package/src/assets/web-panel/assets/{hasIn-BJ7fkx3F.js → hasIn-BeUqVVKU.js} +1 -1
  96. package/src/assets/web-panel/assets/{icons-DEmcnxBf.js → icons-DG5UD-6K.js} +4 -4
  97. package/src/assets/web-panel/assets/{index-CZ6CHiCQ.js → index-4MkfPLGv.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-7GLXvG8p.js → index-7QUwcGTg.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-C1nHEOFE.js → index-B57al0Z5.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Kio1ekYg.js → index-BER98o4P.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-D9QEc7Tq.js → index-BNpJf-sf.js} +1 -1
  102. package/src/assets/web-panel/assets/{index--CcGiwAI.js → index-BZ1N07Sp.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BWPey0Uf.js → index-BlfTf0ka.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-B7Fah_BX.js → index-BohrJpBf.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BJ8-oLHI.js → index-Br3SOdER.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-D2qNdrNW.js → index-C0_sKVi7.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CQ-xLCVb.js → index-CAdCTTBC.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DSRIjAWO.js → index-CExWtm98.js} +1 -1
  109. package/src/assets/web-panel/assets/{index--Xb2lq6E.js → index-CL71rIwo.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BDkN6YOI.js → index-CfYRv-zA.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Bk5yST66.js → index-CnnxzIx0.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-2N9Y1cLx.js → index-CrnVEEQO.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CW6n-y1L.js → index-CzT56x-_.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-OaNFXV8N.js → index-D4b5XqIy.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CEPTiBWy.js → index-D84BnuFg.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CZCTan8Q.js → index-D8r7WKk-.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-rn6xd12s.js → index-DA0-EJKx.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BcmXbIwv.js → index-DBzBLX-K.js} +3 -3
  119. package/src/assets/web-panel/assets/{index-C--PPqwi.js → index-DFFzwrNs.js} +1 -1
  120. package/src/assets/web-panel/assets/index-DJ8etkBT.js +1 -0
  121. package/src/assets/web-panel/assets/{index-Ty4w18mR.js → index-DMT8n9vB.js} +4 -4
  122. package/src/assets/web-panel/assets/{index-qOBKrw-g.js → index-DQor_7NF.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CN6emwbq.js → index-DT40zd7K.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-BM0mQhVv.js → index-De85D9Pc.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-ceH9DsAi.js → index-IGGttsCv.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DBhkJqVF.js → index-L73NrmlW.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BbZynivw.js → index-LMo9bBLJ.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Bvheiep2.js → index-MKZrJrP_.js} +1 -1
  129. package/src/assets/web-panel/assets/index-OeaK-zXZ.js +1 -0
  130. package/src/assets/web-panel/assets/{index-DxX8hhmL.js → index-WRw1pM5E.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-B6WoW8DJ.js → index-YKLCotoo.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-BP7HFsAK.js → index-hjgrIX5I.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-CnjtAKdw.js → index-m9bn0F1p.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-CEq5Vh0i.js → index-wesQQ2cH.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-B1-h2QrC.js → index-x7VYlV41.js} +1 -1
  136. package/src/assets/web-panel/assets/{initDefaultProps-BmxMBuUZ.js → initDefaultProps-DGeU4vPS.js} +1 -1
  137. package/src/assets/web-panel/assets/{motion-DnXNwo2k.js → motion-Dgqj9ab3.js} +1 -1
  138. package/src/assets/web-panel/assets/{move-WnIJwmZN.js → move-BFyEU6Lr.js} +1 -1
  139. package/src/assets/web-panel/assets/{mtc-parser-DAto-c2w.js → mtc-parser-CueCXXPC.js} +1 -1
  140. package/src/assets/web-panel/assets/{omit-BtvW9W0H.js → omit-D2pU5OyE.js} +1 -1
  141. package/src/assets/web-panel/assets/{pickAttrs-DFqI9-_C.js → pickAttrs-CKTf6zD6.js} +1 -1
  142. package/src/assets/web-panel/assets/{placementArrow-Dax6wOK3.js → placementArrow-DieJaGCN.js} +1 -1
  143. package/src/assets/web-panel/assets/{responsiveObserve-BUFWWC83.js → responsiveObserve-Bxr-rmU6.js} +1 -1
  144. package/src/assets/web-panel/assets/{slide-DyARyuSz.js → slide-CDEDk3Se.js} +1 -1
  145. package/src/assets/web-panel/assets/{statusUtils-BZSDugpi.js → statusUtils-icf2eCTx.js} +1 -1
  146. package/src/assets/web-panel/assets/{styleChecker-B7o_K-4R.js → styleChecker-qKvgmBhE.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFlexGapSupport-Dg7Bg8VG.js → useFlexGapSupport-1_UhiyJZ.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFs-DQYXo5Rk.js → useFs-CFGLXDWh.js} +1 -1
  149. package/src/assets/web-panel/assets/{usePersonalDataHub-E8NBAnkq.js → usePersonalDataHub-UafOXvQ6.js} +1 -1
  150. package/src/assets/web-panel/assets/{vnode-DD71_w1b.js → vnode-BfPqSx79.js} +1 -1
  151. package/src/assets/web-panel/assets/{zoom-zc9EMd3q.js → zoom-CXsVcRB6.js} +1 -1
  152. package/src/assets/web-panel/index.html +2 -2
  153. package/src/command-manifest.json +22 -1
  154. package/src/commands/agenda.js +228 -0
  155. package/src/commands/agent.js +45 -4
  156. package/src/commands/artifacts.js +161 -0
  157. package/src/commands/batch.js +345 -0
  158. package/src/commands/browse.js +204 -2
  159. package/src/data/changelog.json +13 -2
  160. package/src/gateways/ws/artifact-protocol.js +219 -0
  161. package/src/gateways/ws/message-dispatcher.js +6 -0
  162. package/src/gateways/ws/ws-server.js +32 -0
  163. package/src/index.js +18 -0
  164. package/src/lib/accessibility.js +55 -0
  165. package/src/lib/agent-batch.js +235 -0
  166. package/src/lib/agent-notify.js +66 -0
  167. package/src/lib/agent-schedule-store.js +330 -0
  168. package/src/lib/artifact-store.js +236 -0
  169. package/src/lib/chrome-connector.js +314 -0
  170. package/src/lib/hook-runner.cjs +47 -7
  171. package/src/lib/ide-context.js +84 -0
  172. package/src/lib/permission-rules.cjs +26 -3
  173. package/src/lib/plugin-runtime/scopes.js +12 -0
  174. package/src/lib/safe-mode.js +40 -0
  175. package/src/lib/shell-selector.cjs +203 -0
  176. package/src/lib/skill-loader.js +19 -0
  177. package/src/lib/slash-dispatch.js +34 -0
  178. package/src/repl/agent-repl.js +99 -58
  179. package/src/runtime/agent-core.js +197 -8
  180. package/src/runtime/coding-agent-contract-shared.cjs +156 -0
  181. package/src/runtime/coding-agent-policy.cjs +43 -0
  182. package/src/runtime/headless-stream.js +51 -1
  183. package/src/runtime/policies/agent-policy.js +1 -0
  184. package/src/assets/web-panel/assets/AppLayout-C6WIrDZk.js +0 -9
  185. package/src/assets/web-panel/assets/ChatBubbleRenderer-BFEnA8LN.js +0 -1
  186. package/src/assets/web-panel/assets/Inference-CoK8Ia0Z.js +0 -1
  187. package/src/assets/web-panel/assets/Marketplace-DFqhJMuY.js +0 -1
  188. package/src/assets/web-panel/assets/Reputation-CaRxB3Ep.js +0 -1
  189. package/src/assets/web-panel/assets/Search-BpjLBV8u.js +0 -1
  190. package/src/assets/web-panel/assets/Skills-DoTWIQOX.js +0 -1
  191. package/src/assets/web-panel/assets/Tasks-B3hEXNcI.js +0 -1
  192. package/src/assets/web-panel/assets/devWarning-Dr9dzlQ5.js +0 -1
  193. package/src/assets/web-panel/assets/index-B92M3t6B.js +0 -1
  194. package/src/assets/web-panel/assets/index-d2UcGpK-.js +0 -1
@@ -0,0 +1,330 @@
1
+ /**
2
+ * AgentScheduleStore — persistent backing for the agent-callable Monitor /
3
+ * Cron / Wakeup tools (gap-analysis 第四阶段 #3).
4
+ *
5
+ * A one-shot `cc agent` turn cannot itself keep a timer alive, so these tools
6
+ * PERSIST their intent to a JSONL store under
7
+ * `~/.chainlesschain/agent-schedule/<kind>.jsonl`; a supervising loop / cron
8
+ * (`cc agenda run`) reads due entries and acts. This mirrors how Claude Code's
9
+ * Monitor/CronCreate/ScheduleWakeup outlive a single turn.
10
+ *
11
+ * Three entry kinds share one store:
12
+ * - wakeup one-shot: run `prompt` (a `cc agent -p`) once at/after `dueAt`.
13
+ * - cron recurring: run `prompt` on a cron schedule; `nextAt` advances.
14
+ * - monitor recurring watch: run `command` (shell) every `intervalMs`;
15
+ * fire the `notify` payload when `stopWhen` regex matches output.
16
+ *
17
+ * Pure over an injected clock + directory so it is fully unit-testable. Cron
18
+ * parsing is a compact 5-field evaluator (min hour dom mon dow) supporting
19
+ * `* , - /` — enough for the scheduling the agent actually asks for.
20
+ */
21
+
22
+ import fs from "fs";
23
+ import os from "os";
24
+ import path from "path";
25
+ import { randomUUID } from "crypto";
26
+
27
+ export const SCHEDULE_KINDS = Object.freeze(["wakeup", "cron", "monitor"]);
28
+
29
+ export function agentScheduleDir(homedir = os.homedir()) {
30
+ return path.join(homedir, ".chainlesschain", "agent-schedule");
31
+ }
32
+
33
+ // ─── cron (5-field) ────────────────────────────────────────────────────────
34
+
35
+ const CRON_RANGES = [
36
+ [0, 59], // minute
37
+ [0, 23], // hour
38
+ [1, 31], // day of month
39
+ [1, 12], // month
40
+ [0, 6], // day of week (0/7 = Sunday)
41
+ ];
42
+
43
+ function parseCronField(field, [min, max]) {
44
+ const values = new Set();
45
+ for (const part of String(field).split(",")) {
46
+ const [rangePart, stepPart] = part.split("/");
47
+ const step = stepPart ? parseInt(stepPart, 10) : 1;
48
+ if (!Number.isFinite(step) || step < 1) {
49
+ throw new Error(`invalid cron step in "${field}"`);
50
+ }
51
+ let lo = min;
52
+ let hi = max;
53
+ if (rangePart !== "*" && rangePart !== "") {
54
+ const bounds = rangePart.split("-");
55
+ lo = parseInt(bounds[0], 10);
56
+ hi = bounds.length > 1 ? parseInt(bounds[1], 10) : lo;
57
+ if (!Number.isFinite(lo) || !Number.isFinite(hi)) {
58
+ throw new Error(`invalid cron range in "${field}"`);
59
+ }
60
+ }
61
+ for (let v = lo; v <= hi; v += step) {
62
+ if (v < min || v > max) throw new Error(`cron value out of range: ${v}`);
63
+ values.add(v);
64
+ }
65
+ }
66
+ return values;
67
+ }
68
+
69
+ /** Parse a 5-field cron expression into per-field value sets. Throws on junk. */
70
+ export function parseCron(expr) {
71
+ const fields = String(expr).trim().split(/\s+/);
72
+ if (fields.length !== 5) {
73
+ throw new Error(
74
+ `cron expression must have 5 fields (min hour dom mon dow), got ${fields.length}`,
75
+ );
76
+ }
77
+ return fields.map((field, i) => parseCronField(field, CRON_RANGES[i]));
78
+ }
79
+
80
+ function cronMatches(sets, date) {
81
+ const dow = date.getDay(); // 0-6, Sun=0
82
+ return (
83
+ sets[0].has(date.getMinutes()) &&
84
+ sets[1].has(date.getHours()) &&
85
+ sets[2].has(date.getDate()) &&
86
+ sets[3].has(date.getMonth() + 1) &&
87
+ (sets[4].has(dow) || (sets[4].has(7) && dow === 0))
88
+ );
89
+ }
90
+
91
+ /**
92
+ * Next epoch-ms at which `expr` fires strictly after `fromMs` (local time,
93
+ * minute granularity). Scans up to ~366 days then gives up (returns null) so a
94
+ * never-matching expression can't loop forever.
95
+ */
96
+ export function nextCronTime(expr, fromMs) {
97
+ const sets = parseCron(expr);
98
+ const cursor = new Date(fromMs);
99
+ cursor.setSeconds(0, 0);
100
+ cursor.setMinutes(cursor.getMinutes() + 1);
101
+ const limit = 366 * 24 * 60;
102
+ for (let i = 0; i < limit; i += 1) {
103
+ if (cronMatches(sets, cursor)) return cursor.getTime();
104
+ cursor.setMinutes(cursor.getMinutes() + 1);
105
+ }
106
+ return null;
107
+ }
108
+
109
+ // ─── store ───────────────────────────────────────────────────────────────
110
+
111
+ export class AgentScheduleStore {
112
+ constructor({ dir = null, now = () => Date.now() } = {}) {
113
+ this.dir = dir || agentScheduleDir();
114
+ this._now = typeof now === "function" ? now : () => now;
115
+ }
116
+
117
+ _file(kind) {
118
+ return path.join(this.dir, `${kind}.jsonl`);
119
+ }
120
+
121
+ _ensureDir() {
122
+ fs.mkdirSync(this.dir, { recursive: true, mode: 0o700 });
123
+ }
124
+
125
+ _readAll(kind) {
126
+ let raw;
127
+ try {
128
+ raw = fs.readFileSync(this._file(kind), "utf-8");
129
+ } catch {
130
+ return [];
131
+ }
132
+ const entries = [];
133
+ for (const line of raw.split("\n")) {
134
+ const trimmed = line.trim();
135
+ if (!trimmed) continue;
136
+ try {
137
+ entries.push(JSON.parse(trimmed));
138
+ } catch {
139
+ // Skip a corrupt line rather than throwing — one bad append must not
140
+ // poison the whole store (per-row resilience, like the JSON.parse sweep).
141
+ }
142
+ }
143
+ return entries;
144
+ }
145
+
146
+ _writeAll(kind, entries) {
147
+ this._ensureDir();
148
+ const body = entries.map((entry) => JSON.stringify(entry)).join("\n");
149
+ fs.writeFileSync(this._file(kind), body ? body + "\n" : "", {
150
+ encoding: "utf-8",
151
+ mode: 0o600,
152
+ });
153
+ }
154
+
155
+ // ── create ──────────────────────────────────────────────────────────────
156
+
157
+ scheduleWakeup({ prompt, delayMs = 0, dueAt = null, label = null } = {}) {
158
+ if (!prompt || typeof prompt !== "string") {
159
+ throw new Error("wakeup requires a prompt");
160
+ }
161
+ const now = this._now();
162
+ const entry = {
163
+ id: randomUUID(),
164
+ kind: "wakeup",
165
+ prompt,
166
+ label: label || null,
167
+ dueAt:
168
+ dueAt != null ? Number(dueAt) : now + Math.max(0, Number(delayMs) || 0),
169
+ createdAt: now,
170
+ status: "pending",
171
+ };
172
+ const entries = this._readAll("wakeup");
173
+ entries.push(entry);
174
+ this._writeAll("wakeup", entries);
175
+ return entry;
176
+ }
177
+
178
+ createCron({ prompt, cron, label = null } = {}) {
179
+ if (!prompt || typeof prompt !== "string") {
180
+ throw new Error("cron requires a prompt");
181
+ }
182
+ const now = this._now();
183
+ const nextAt = nextCronTime(cron, now); // validates the expression too
184
+ if (nextAt == null) {
185
+ throw new Error(`cron "${cron}" has no upcoming match`);
186
+ }
187
+ const entry = {
188
+ id: randomUUID(),
189
+ kind: "cron",
190
+ prompt,
191
+ cron,
192
+ label: label || null,
193
+ nextAt,
194
+ createdAt: now,
195
+ lastRunAt: null,
196
+ runs: 0,
197
+ status: "active",
198
+ };
199
+ const entries = this._readAll("cron");
200
+ entries.push(entry);
201
+ this._writeAll("cron", entries);
202
+ return entry;
203
+ }
204
+
205
+ createMonitor({
206
+ command,
207
+ intervalMs,
208
+ stopWhen = null,
209
+ notify = null,
210
+ maxChecks = null,
211
+ label = null,
212
+ } = {}) {
213
+ if (!command || typeof command !== "string") {
214
+ throw new Error("monitor requires a command");
215
+ }
216
+ const interval = Math.max(1000, Number(intervalMs) || 60000);
217
+ if (stopWhen != null) {
218
+ // Validate the regex up-front so a bad pattern errors at creation, not
219
+ // silently at the first check.
220
+ // eslint-disable-next-line no-new
221
+ new RegExp(stopWhen);
222
+ }
223
+ const now = this._now();
224
+ const entry = {
225
+ id: randomUUID(),
226
+ kind: "monitor",
227
+ command,
228
+ intervalMs: interval,
229
+ stopWhen: stopWhen || null,
230
+ notify: notify || null,
231
+ maxChecks: maxChecks != null ? Number(maxChecks) : null,
232
+ label: label || null,
233
+ nextAt: now + interval,
234
+ createdAt: now,
235
+ checks: 0,
236
+ status: "active",
237
+ };
238
+ const entries = this._readAll("monitor");
239
+ entries.push(entry);
240
+ this._writeAll("monitor", entries);
241
+ return entry;
242
+ }
243
+
244
+ // ── query ─────────────────────────────────────────────────────────────────
245
+
246
+ list(kind = null) {
247
+ const kinds = kind ? [kind] : SCHEDULE_KINDS;
248
+ const out = [];
249
+ for (const k of kinds) out.push(...this._readAll(k));
250
+ return out;
251
+ }
252
+
253
+ /** Entries whose time has come (pending wakeup / active cron|monitor). */
254
+ due(kind = null, atMs = null) {
255
+ const now = atMs != null ? atMs : this._now();
256
+ return this.list(kind).filter((entry) => {
257
+ if (entry.kind === "wakeup") {
258
+ return entry.status === "pending" && entry.dueAt <= now;
259
+ }
260
+ return entry.status === "active" && entry.nextAt <= now;
261
+ });
262
+ }
263
+
264
+ cancel(id) {
265
+ for (const kind of SCHEDULE_KINDS) {
266
+ const entries = this._readAll(kind);
267
+ const idx = entries.findIndex((entry) => entry.id === id);
268
+ if (idx >= 0) {
269
+ const [removed] = entries.splice(idx, 1);
270
+ this._writeAll(kind, entries);
271
+ return removed;
272
+ }
273
+ }
274
+ return null;
275
+ }
276
+
277
+ // ── lifecycle mutations (used by `cc agenda run`) ──────────────────────────
278
+
279
+ /** Mark a one-shot wakeup fired (keeps history; due() skips it). */
280
+ markWakeupFired(id, atMs = null) {
281
+ return this._mutate("wakeup", id, (entry) => {
282
+ entry.status = "fired";
283
+ entry.firedAt = atMs != null ? atMs : this._now();
284
+ });
285
+ }
286
+
287
+ /** Advance a cron entry to its next fire time after running once. */
288
+ advanceCron(id, atMs = null) {
289
+ const now = atMs != null ? atMs : this._now();
290
+ return this._mutate("cron", id, (entry) => {
291
+ entry.lastRunAt = now;
292
+ entry.runs = (entry.runs || 0) + 1;
293
+ const next = nextCronTime(entry.cron, now);
294
+ if (next == null) {
295
+ entry.status = "exhausted";
296
+ } else {
297
+ entry.nextAt = next;
298
+ }
299
+ });
300
+ }
301
+
302
+ /**
303
+ * Record a monitor check. `matched` ends the monitor (done); otherwise it is
304
+ * re-armed for the next interval, or exhausted once maxChecks is hit.
305
+ */
306
+ recordMonitorCheck(id, { matched = false, atMs = null } = {}) {
307
+ const now = atMs != null ? atMs : this._now();
308
+ return this._mutate("monitor", id, (entry) => {
309
+ entry.checks = (entry.checks || 0) + 1;
310
+ entry.lastCheckAt = now;
311
+ if (matched) {
312
+ entry.status = "matched";
313
+ entry.matchedAt = now;
314
+ } else if (entry.maxChecks != null && entry.checks >= entry.maxChecks) {
315
+ entry.status = "exhausted";
316
+ } else {
317
+ entry.nextAt = now + entry.intervalMs;
318
+ }
319
+ });
320
+ }
321
+
322
+ _mutate(kind, id, fn) {
323
+ const entries = this._readAll(kind);
324
+ const entry = entries.find((e) => e.id === id);
325
+ if (!entry) return null;
326
+ fn(entry);
327
+ this._writeAll(kind, entries);
328
+ return entry;
329
+ }
330
+ }
@@ -0,0 +1,236 @@
1
+ /**
2
+ * ArtifactStore — durable home for agent DELIVERABLES (gap-analysis P1 #10).
3
+ *
4
+ * An agent run produces important files — a Markdown report, a patch, a
5
+ * screenshot, test logs, review findings JSON — that otherwise live as loose
6
+ * paths the user has to remember. `publish_artifact` copies such a file into
7
+ * `~/.chainlesschain/artifacts/` with metadata (title, kind, mime, sha256,
8
+ * session), so:
9
+ * - the session transcript only records the small METADATA object (the tool
10
+ * result), never the file body;
11
+ * - `cc artifacts` lists/inspects/cleans them across sessions;
12
+ * - web-panel / remote surfaces can later serve previews from one place.
13
+ *
14
+ * Layout:
15
+ * ~/.chainlesschain/artifacts/index.jsonl one metadata row per artifact
16
+ * ~/.chainlesschain/artifacts/files/<id><ext> the stored copy
17
+ *
18
+ * Per-row tolerant JSONL (a corrupt line never poisons the store); injected
19
+ * clock + dir for unit tests; TTL-based expiry with an explicit cleanup call
20
+ * (`cc artifacts clean` / opportunistic on publish).
21
+ */
22
+
23
+ import fs from "fs";
24
+ import os from "os";
25
+ import path from "path";
26
+ import crypto from "crypto";
27
+
28
+ export const ARTIFACT_KINDS = Object.freeze([
29
+ "report",
30
+ "patch",
31
+ "screenshot",
32
+ "log",
33
+ "data",
34
+ "other",
35
+ ]);
36
+
37
+ /** Copy-size guard: an "artifact" is a deliverable, not a build tree. */
38
+ export const MAX_ARTIFACT_BYTES = 100 * 1024 * 1024; // 100 MB
39
+
40
+ /** Default retention before `clean` removes an artifact. */
41
+ export const DEFAULT_TTL_DAYS = 30;
42
+
43
+ const MIME_BY_EXT = Object.freeze({
44
+ ".md": "text/markdown",
45
+ ".markdown": "text/markdown",
46
+ ".txt": "text/plain",
47
+ ".log": "text/plain",
48
+ ".json": "application/json",
49
+ ".html": "text/html",
50
+ ".htm": "text/html",
51
+ ".csv": "text/csv",
52
+ ".xml": "application/xml",
53
+ ".yaml": "application/yaml",
54
+ ".yml": "application/yaml",
55
+ ".patch": "text/x-patch",
56
+ ".diff": "text/x-patch",
57
+ ".png": "image/png",
58
+ ".jpg": "image/jpeg",
59
+ ".jpeg": "image/jpeg",
60
+ ".gif": "image/gif",
61
+ ".webp": "image/webp",
62
+ ".svg": "image/svg+xml",
63
+ ".pdf": "application/pdf",
64
+ ".zip": "application/zip",
65
+ });
66
+
67
+ export function artifactsDir(homedir = os.homedir()) {
68
+ // Test/deployment override (same convention as CC_BACKGROUND_AGENTS_DIR).
69
+ if (process.env.CC_ARTIFACTS_DIR) return process.env.CC_ARTIFACTS_DIR;
70
+ return path.join(homedir, ".chainlesschain", "artifacts");
71
+ }
72
+
73
+ function mimeForExt(ext) {
74
+ return (
75
+ MIME_BY_EXT[String(ext || "").toLowerCase()] || "application/octet-stream"
76
+ );
77
+ }
78
+
79
+ function normalizeKind(kind) {
80
+ const k = typeof kind === "string" ? kind.trim().toLowerCase() : "";
81
+ return ARTIFACT_KINDS.includes(k) ? k : "other";
82
+ }
83
+
84
+ export class ArtifactStore {
85
+ constructor({ dir = null, now = () => Date.now() } = {}) {
86
+ this.dir = dir || artifactsDir();
87
+ this._now = typeof now === "function" ? now : () => now;
88
+ }
89
+
90
+ _indexFile() {
91
+ return path.join(this.dir, "index.jsonl");
92
+ }
93
+
94
+ _filesDir() {
95
+ return path.join(this.dir, "files");
96
+ }
97
+
98
+ _ensureDirs() {
99
+ fs.mkdirSync(this._filesDir(), { recursive: true });
100
+ }
101
+
102
+ /**
103
+ * Publish a file as an artifact (copy + metadata row).
104
+ * @param {{ filePath: string, title?: string, kind?: string,
105
+ * sessionId?: string|null, ttlDays?: number }} opts
106
+ * @returns {object} the metadata entry (what the tool returns — no body)
107
+ */
108
+ publish({ filePath, title, kind, sessionId = null, ttlDays } = {}) {
109
+ if (!filePath) throw new Error("publish requires a filePath");
110
+ const abs = path.resolve(filePath);
111
+ let stat;
112
+ try {
113
+ stat = fs.statSync(abs);
114
+ } catch {
115
+ throw new Error(`artifact source not found: ${abs}`);
116
+ }
117
+ if (!stat.isFile()) {
118
+ throw new Error(`artifact source is not a regular file: ${abs}`);
119
+ }
120
+ if (stat.size > MAX_ARTIFACT_BYTES) {
121
+ throw new Error(
122
+ `artifact too large (${stat.size} bytes > ${MAX_ARTIFACT_BYTES}); artifacts are deliverables, not build output`,
123
+ );
124
+ }
125
+ const body = fs.readFileSync(abs);
126
+ const sha256 = crypto.createHash("sha256").update(body).digest("hex");
127
+ const ext = path.extname(abs);
128
+ const id = `art_${this._now().toString(36)}_${crypto
129
+ .randomBytes(4)
130
+ .toString("hex")}`;
131
+ const days = Number.isFinite(Number(ttlDays))
132
+ ? Math.max(1, Number(ttlDays))
133
+ : DEFAULT_TTL_DAYS;
134
+ const createdAt = this._now();
135
+ const entry = {
136
+ id,
137
+ title: String(title || path.basename(abs)),
138
+ kind: normalizeKind(kind),
139
+ mime: mimeForExt(ext),
140
+ size: stat.size,
141
+ sha256,
142
+ sourcePath: abs,
143
+ file: `${id}${ext}`,
144
+ sessionId: sessionId ? String(sessionId) : null,
145
+ createdAt: new Date(createdAt).toISOString(),
146
+ expiresAt: new Date(createdAt + days * 24 * 60 * 60 * 1000).toISOString(),
147
+ };
148
+ this._ensureDirs();
149
+ fs.writeFileSync(path.join(this._filesDir(), entry.file), body);
150
+ fs.appendFileSync(this._indexFile(), JSON.stringify(entry) + "\n", "utf-8");
151
+ return entry;
152
+ }
153
+
154
+ /** All (non-corrupt) metadata rows, oldest first. */
155
+ list({ sessionId } = {}) {
156
+ let raw;
157
+ try {
158
+ raw = fs.readFileSync(this._indexFile(), "utf-8");
159
+ } catch {
160
+ return [];
161
+ }
162
+ const out = [];
163
+ for (const line of raw.split("\n")) {
164
+ const t = line.trim();
165
+ if (!t) continue;
166
+ try {
167
+ const e = JSON.parse(t);
168
+ if (e && typeof e === "object" && e.id) out.push(e);
169
+ } catch {
170
+ /* per-row tolerance — a corrupt line never poisons the store */
171
+ }
172
+ }
173
+ return sessionId ? out.filter((e) => e.sessionId === sessionId) : out;
174
+ }
175
+
176
+ /** One entry by id (or null). */
177
+ get(id) {
178
+ return this.list().find((e) => e.id === id) || null;
179
+ }
180
+
181
+ /** Absolute path of an artifact's stored copy (or null). */
182
+ storedPath(idOrEntry) {
183
+ const entry =
184
+ typeof idOrEntry === "object" && idOrEntry
185
+ ? idOrEntry
186
+ : this.get(idOrEntry);
187
+ if (!entry) return null;
188
+ return path.join(this._filesDir(), entry.file);
189
+ }
190
+
191
+ /** Remove one artifact (file + index row). @returns {boolean} found */
192
+ remove(id) {
193
+ const entries = this.list();
194
+ const target = entries.find((e) => e.id === id);
195
+ if (!target) return false;
196
+ try {
197
+ fs.rmSync(this.storedPath(target), { force: true });
198
+ } catch {
199
+ /* best-effort — the index row is the source of truth */
200
+ }
201
+ this._rewrite(entries.filter((e) => e.id !== id));
202
+ return true;
203
+ }
204
+
205
+ /**
206
+ * Remove expired artifacts (expiresAt in the past).
207
+ * @returns {{ removed: number }}
208
+ */
209
+ cleanupExpired() {
210
+ const entries = this.list();
211
+ const now = this._now();
212
+ const keep = [];
213
+ let removed = 0;
214
+ for (const e of entries) {
215
+ const exp = Date.parse(e.expiresAt || "");
216
+ if (Number.isFinite(exp) && exp <= now) {
217
+ try {
218
+ fs.rmSync(this.storedPath(e), { force: true });
219
+ } catch {
220
+ /* best-effort */
221
+ }
222
+ removed += 1;
223
+ } else {
224
+ keep.push(e);
225
+ }
226
+ }
227
+ if (removed > 0) this._rewrite(keep);
228
+ return { removed };
229
+ }
230
+
231
+ _rewrite(entries) {
232
+ this._ensureDirs();
233
+ const text = entries.map((e) => JSON.stringify(e)).join("\n");
234
+ fs.writeFileSync(this._indexFile(), text ? text + "\n" : "", "utf-8");
235
+ }
236
+ }