chainlesschain 0.162.158 → 0.162.159

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 (201) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-BwpTJTN_.js → AIOps-ByIiF5bT.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BtQOoo3l.js → ActionButton-NlgSFZq5.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-Duf_rXZa.js → Analytics-D17cOUcb.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-XQK3wjvB.js → AppLayout-B4mEZcbl.js} +4 -4
  6. package/src/assets/web-panel/assets/Artifacts-CASsmyQw.js +1 -0
  7. package/src/assets/web-panel/assets/{Audit-DNjOGQLh.js → Audit-DYAia2e2.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-FH9RAfB5.js → BackgroundAgents-C1lvYUSu.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-ZIbrR4-U.js → Backup-B6agK6bA.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-DSt3NaND.js → BaseInput-DM43R2vy.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-CY-wvbjv.js → Chat-Dtdp3eXp.js} +5 -5
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-BAV9V-t-.js → ChatBubbleRenderer-BL5c_OQ1.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-8JVadE_B.js → Checkbox-D2xf7uZt.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-CN83nPYI.js → Codegen-D3j45jJL.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-xPSvqdN8.js → Col-voPJ9fUZ.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-D78cnIjG.js → Community-DEsyTESF.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-CSw6Csbp.js → Compact-OHV_QXLD.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-E_HhjcYR.js → Compliance-eDTJWC-J.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-DybeQjmm.js → Cowork-BBvy-6d1.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-DS0BJeLY.js → Cron-Dw2eoJxu.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-BOwVgkbs.js → Crosschain-DQHms_Bh.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-DRUr9YuX.js → DID-Cyu7Id1c.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-Cht-yUKW.js → Dashboard-sIDzkG92.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-BLr_EJ9X.js → Dropdown-DUW3T2ey.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-Drexuh5W.js → EmailListRenderer-CECELXW8.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-D9Q2Kq63.js → FamilyGuardDashboard-BIZR37ov.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-C-Z1xI-T.js → Federation-BEZIQX84.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-DLHPMkQF.js → FormItemContext-BcdUMznL.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-DxxEdn2r.js → GenericCardRenderer-DsN_fr5H.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-syATcaBt.js → Git-63LhjBx2.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-CYe_r8Zf.js → Governance-CYtQtIcH.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-GWxErB7k.js → Inference-BwR6UUox.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-qQvVCZoi.js → KnowledgeGraph-D9lVNTUA.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-BenAU2W8.js → Logs-C4q5h7Ut.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-7agE3Or1.js → MarkdownRenderer-DexLlXfN.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-D4Jn5fpD.js → Marketplace-Plksliuw.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-DtAwoqiQ.js → McpTools-DZUFfeuG.js} +4 -4
  38. package/src/assets/web-panel/assets/{Memory-BI_O50I8.js → Memory-NoDYt29V.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-CQWE2lqx.js → MobileBridge-CEXGTkdh.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-CVtTqgtg.js → MobileProjects-DnbJkbZo.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-Dnr16qsn.js → Mtc-DTAa0cwy.js} +5 -5
  42. package/src/assets/web-panel/assets/{MtcAudit-CQ5Xe4-a.js → MtcAudit-CVV7LnNS.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-Bjr8O-wy.js → Multisig-BqqGrjt_.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-CEe-KirU.js → NLProgramming-C4WP8DRp.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-spKJIKF9.js → Notes-Zo4DJUVI.js} +4 -4
  46. package/src/assets/web-panel/assets/{NotificationSettings-DRp83LFG.js → NotificationSettings-DBcjRaz2.js} +1 -1
  47. package/src/assets/web-panel/assets/OrderTableRenderer-BCQel9bD.js +1 -0
  48. package/src/assets/web-panel/assets/{Organization-BlgB0IYg.js → Organization-BT9WPBOX.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-a_35HqZI.js → Overflow-D6b4bZ3q.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-CSNHCNPC.js → P2P-Cw3OsYx-.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-C8M1JH-1.js → PdhVaultBrowser-C0nyiK2b.js} +5 -5
  52. package/src/assets/web-panel/assets/{Permissions-DmjJi9Ms.js → Permissions-zVBZEB_C.js} +3 -3
  53. package/src/assets/web-panel/assets/{PersonalDataHub-DfuwRyf4.js → PersonalDataHub-BOspsFew.js} +2 -2
  54. package/src/assets/web-panel/assets/{Pipeline-B2-S4ekE.js → Pipeline-CQWfZ4XH.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-B1dOiBLC.js → Privacy-C2oGJ_WL.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-_pNmc-DX.js → ProjectInit-BZMR4qY1.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-DmwG2Ii1.js → ProjectSettings-DAujoP63.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-COATmHxF.js → Projects-DvH_P2Uq.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-C6HSjg1d.js → Providers-D_RGHM-v.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-CrDmS7l1.js → QrScannerModal-Mf6KfL2J.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-CfKvWt-x.js → QuickAsk-5D8URoI_.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-DMa6Kuzv.js → Recommend-Df4kC8Gy.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-DrktUo5x.js → RemoteSession-DGi3f3vO.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-BTPQ1vjL.js → Reputation-jmkGFNWb.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-B6Q7QkMG.js → Row-DP6Pcu3m.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-B1IJhAmX.js → RssFeed-D60hSuPO.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-xbea1kGD.js → Search-0mf1BQUK.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-7rs0cO3F.js → Security-CfZtLFYV.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-0fjixK0S.js → Services-D9zpZLWz.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-B_rVq9Cu.js → Skeleton-D1_44eMD.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-C5uM90b9.js → Skills-cVFa8zkz.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-U4pYCIZH.js → Sla-Cinpzc3L.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-Cdz4yTWx.js → SpeechSettings-bqdiJE_P.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-Me3aetHv.js → SyncSettings-Dgl_SLgj.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-Cf6aNkL_.js → Tasks-DawWAEhy.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-CYatk4tU.js → Templates-Ca0nY651.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-DIMKLw7o.js → Tenant-CgaGEhLm.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-C_cp-uSJ.js → Terminal-D-9EnpRx.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-cUPVNADp.js → TimelineRenderer-CSOxDHZ6.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-CiaEIXVf.js → Tokens-BpwLZpL3.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-DB1WU5lQ.js → Trigger-DgowwBK5.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-Cfht9Ijy.js → Trust-BZutapjE.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-K3P4Vt9S.js → UkeySign-QZPt8_Sp.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-DWjhjXRh.js → VideoEditing-DlQx5UDJ.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-D9-Q1Kan.js → Wallet-ZfdTgtWA.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-BDszy-oV.js → WebAuthn-CAp0y_Cr.js} +5 -5
  87. package/src/assets/web-panel/assets/{WorkflowEditor-DIhij4rA.js → WorkflowEditor-BVITZEmr.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-D7VCiKbh.js → chat-CC2tPaNg.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-BvOd9pVn.js → colors-31brc9CA.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-DoqXBiAO.js → compact-item-DLMkC9C-.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-CrKRzUtp.js → createContext-BMtveI6F.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-CB8pPOtd.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-BWAev4WC.js → hasIn-DbD6WHW2.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-B30NvR21.js → index-3oBsXZmF.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Cx7M7PSF.js → index-ALvoNTRF.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BHOcVYrx.js → index-AaIKG60c.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BXyl8O23.js → index-BFRLmCR3.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-YGDy7nNq.js → index-BKAB6-6V.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BoYrS_L2.js → index-BOM-VFlv.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Bq_ctRpz.js → index-BPpS-lGb.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CDGLdn12.js → index-Bbc2JeQG.js} +1 -1
  102. package/src/assets/web-panel/assets/index-BhXiaiev.js +1 -0
  103. package/src/assets/web-panel/assets/{index-ISvPZ05r.js → index-BiRQUoyE.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-B0vGEbJT.js → index-BnFLzejT.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-Bj7EgmOp.js → index-BoO4WEJB.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BZGk8AbZ.js → index-C-cGlCDW.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BkgnGX7v.js → index-C1PfeI88.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-HwHdTusY.js → index-C21pdBqs.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Bf9YlbDn.js → index-C7LHNw_N.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-oY45XrkG.js → index-CEXa-4zd.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-KR1-Em9C.js → index-CEwjBA9q.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-C5kRmj3P.js → index-CFXKbOek.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BN9m4Ux2.js → index-CJhREOlG.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-PDxGjKRu.js → index-COHHQ9cU.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-D23L1V76.js → index-CUmLMFsy.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-zH-tuuGZ.js → index-CWgfipcC.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CmJ6xKBO.js → index-C_eUKGYP.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CEPHMSih.js → index-CcVszn2b.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DV-Rfmfx.js → index-Cod97Urs.js} +3 -3
  120. package/src/assets/web-panel/assets/{index-BWWsaimP.js → index-Cwbqh0KZ.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-8g5TJCZ3.js → index-D67kIagl.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CEXfKSiI.js → index-D7cV0tu0.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CoD31Ndm.js → index-DJvxFyIz.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CSb-tnIo.js → index-DZl-UXB6.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DvDOpsJB.js → index-Dm0byjm1.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DlGu7As6.js → index-DndNnYc0.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-gmxqu5Kk.js → index-WQUO2vvK.js} +1 -1
  128. package/src/assets/web-panel/assets/index-WpaYaE3D.js +1 -0
  129. package/src/assets/web-panel/assets/{index-ww1QtLnX.js → index-bdaJwTXJ.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-JzUmJMvn.js → index-ep6JchNG.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BCyP071m.js → index-jG6h22Qg.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-1poMpMU0.js → index-ntVQqSML.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-XW-OR3mL.js → initDefaultProps-D5UoBRhN.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-CeuEHh20.js → motion-DltOw0CR.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-Dva79Keg.js → move-_oPkk5zj.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-C0B6bD2j.js → mtc-parser-EES8ROCS.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-AkZvO-a2.js → omit-B-_39x_M.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-B4r55qGc.js → pickAttrs-l6D0yJHz.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-BLH-z7YE.js → placementArrow-DIeUK31L.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-ksEGZ2Wf.js → responsiveObserve-dG4EzUQo.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-Cth4OD1Z.js → slide-DWFzYGdH.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-9S83ezxw.js → statusUtils-CECdTA5H.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-7yQmQGZ4.js → styleChecker-D6kP-mMK.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-DpGnMmPc.js → useFlexGapSupport-BWfna_rd.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-C4zDZIXH.js → useFs-Bb-j6rzo.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-CoLICdUZ.js → usePersonalDataHub-D5OIsYM4.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-CKyH-35X.js → vnode-DNpwzFTM.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-DRDGit17.js → zoom-vNLpFQBW.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/command-manifest.json +17 -3
  151. package/src/commands/agent.js +7 -0
  152. package/src/commands/background-session.js +143 -3
  153. package/src/commands/browse.js +86 -0
  154. package/src/commands/cloud.js +232 -0
  155. package/src/commands/doctor.js +61 -6
  156. package/src/commands/mcp.js +61 -0
  157. package/src/commands/plugin.js +63 -1
  158. package/src/commands/routine.js +367 -0
  159. package/src/commands/session.js +326 -0
  160. package/src/data/changelog.json +7 -2
  161. package/src/harness/jsonl-session-store.js +82 -2
  162. package/src/harness/session-index.js +295 -0
  163. package/src/harness/session-mirror.js +142 -0
  164. package/src/harness/transcript-integrity.js +182 -0
  165. package/src/index.js +8 -0
  166. package/src/lib/background-agent-supervisor.js +126 -2
  167. package/src/lib/channels/channel-manager.js +126 -0
  168. package/src/lib/channels/telegram-channel.js +112 -0
  169. package/src/lib/channels/webhook-channel.js +140 -0
  170. package/src/lib/chrome-connector.js +386 -0
  171. package/src/lib/cloud/bundle.js +110 -0
  172. package/src/lib/cloud/cloud-client.js +152 -0
  173. package/src/lib/computer-use/authorization.js +173 -0
  174. package/src/lib/computer-use/computer-use-server.js +276 -0
  175. package/src/lib/computer-use/control-backend.js +150 -0
  176. package/src/lib/doctor-checkup.js +652 -0
  177. package/src/lib/jsonl-session-store.js +4 -0
  178. package/src/lib/lsp/lsp-server-registry.js +48 -21
  179. package/src/lib/plugin-runtime/governance.js +174 -0
  180. package/src/lib/pr-link-ledger.js +221 -0
  181. package/src/lib/routine-store.js +352 -0
  182. package/src/lib/session-store-guard.js +40 -0
  183. package/src/lib/session-usage.js +318 -2
  184. package/src/lib/sub-agent-context.js +23 -0
  185. package/src/repl/agent-repl.js +361 -3
  186. package/src/repl/bg-dashboard.js +355 -0
  187. package/src/repl/fast-mode.js +103 -0
  188. package/src/repl/fullscreen-tui.js +197 -0
  189. package/src/repl/slash-command-registry.js +11 -3
  190. package/src/repl/voice-dictation.js +132 -0
  191. package/src/runtime/agent-core.js +242 -1
  192. package/src/runtime/coding-agent-contract-shared.cjs +88 -0
  193. package/src/runtime/coding-agent-policy.cjs +17 -0
  194. package/src/runtime/headless-runner.js +71 -7
  195. package/src/runtime/headless-stream.js +14 -8
  196. package/src/runtime/policies/agent-policy.js +3 -0
  197. package/src/assets/web-panel/assets/Artifacts-DQvJoQ6E.js +0 -1
  198. package/src/assets/web-panel/assets/OrderTableRenderer-h1A0XB2a.js +0 -1
  199. package/src/assets/web-panel/assets/devWarning-nDB--2xr.js +0 -1
  200. package/src/assets/web-panel/assets/index-B1_pRTtB.js +0 -1
  201. package/src/assets/web-panel/assets/index-TMM9FuW-.js +0 -1
@@ -0,0 +1,352 @@
1
+ /**
2
+ * Routines (gap-2026-07-11 P1#8) — the durable layer above `cc agenda`:
3
+ * named, enable/disable-able agent tasks with four trigger kinds, a run
4
+ * history, and per-run result/usage summaries.
5
+ *
6
+ * trigger kinds:
7
+ * cron — 5-field schedule, fired by the `cc routine run` driver
8
+ * once — a single ISO/epoch time, fired by the driver then disabled
9
+ * webhook — fired externally via `cc routine trigger <id>` (that command
10
+ * IS the API/webhook entry point — wire any HTTP receiver or
11
+ * inbound channel to invoke it)
12
+ * github — driver polls `gh api repos/<repo>/events` and fires when new
13
+ * events of the requested types appear (needs gh auth)
14
+ *
15
+ * Storage: ~/.chainlesschain/routines/
16
+ * routines.json definitions (id → routine)
17
+ * runs.jsonl append-only run history
18
+ * logs/<runId>.log full agent output per run
19
+ *
20
+ * Execution is delegated to the injected `runAgent` (production: spawn
21
+ * `cc agent -p <prompt> --output-format json` and parse the result envelope
22
+ * for usage/cost). Everything here is dependency-injected and unit-testable.
23
+ */
24
+
25
+ import fs from "node:fs";
26
+ import path from "node:path";
27
+ import os from "node:os";
28
+ import { randomBytes } from "node:crypto";
29
+ import { parseCron, nextCronTime } from "./agent-schedule-store.js";
30
+
31
+ export const ROUTINE_TRIGGER_KINDS = Object.freeze([
32
+ "cron",
33
+ "once",
34
+ "webhook",
35
+ "github",
36
+ ]);
37
+
38
+ export function routinesDir(homedir = os.homedir()) {
39
+ return path.join(homedir, ".chainlesschain", "routines");
40
+ }
41
+
42
+ function shortId() {
43
+ return `rt-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
44
+ }
45
+
46
+ function parseWhen(value) {
47
+ if (value == null) return NaN;
48
+ const n = Number(value);
49
+ if (Number.isFinite(n)) return n;
50
+ return Date.parse(String(value));
51
+ }
52
+
53
+ export class RoutineStore {
54
+ constructor({ dir = null, now = () => Date.now() } = {}) {
55
+ this.dir = dir || routinesDir();
56
+ this._now = typeof now === "function" ? now : () => now;
57
+ }
58
+
59
+ _routinesFile() {
60
+ return path.join(this.dir, "routines.json");
61
+ }
62
+ _runsFile() {
63
+ return path.join(this.dir, "runs.jsonl");
64
+ }
65
+ logFile(runId) {
66
+ return path.join(this.dir, "logs", `${runId}.log`);
67
+ }
68
+ _ensureDir() {
69
+ fs.mkdirSync(path.join(this.dir, "logs"), { recursive: true, mode: 0o700 });
70
+ }
71
+
72
+ _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
+ }
79
+ }
80
+ _writeRoutines(map) {
81
+ this._ensureDir();
82
+ fs.writeFileSync(
83
+ this._routinesFile(),
84
+ JSON.stringify(map, null, 2),
85
+ "utf-8",
86
+ );
87
+ }
88
+
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>");
117
+ }
118
+
119
+ const routine = {
120
+ id: shortId(),
121
+ name,
122
+ prompt,
123
+ trigger,
124
+ enabled: true,
125
+ createdAt: this._now(),
126
+ lastFiredAt: null,
127
+ lastSeenGithubEventId: null,
128
+ };
129
+ const map = this._readRoutines();
130
+ map[routine.id] = routine;
131
+ this._writeRoutines(map);
132
+ return routine;
133
+ }
134
+
135
+ list() {
136
+ return Object.values(this._readRoutines()).sort(
137
+ (a, b) => (b.createdAt || 0) - (a.createdAt || 0),
138
+ );
139
+ }
140
+
141
+ get(id) {
142
+ 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;
153
+ }
154
+
155
+ 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;
163
+ }
164
+
165
+ setEnabled(id, enabled) {
166
+ return this.update(id, { enabled: enabled === true });
167
+ }
168
+
169
+ 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;
176
+ }
177
+
178
+ /** Routines the driver should fire now (cron due / once reached). */
179
+ due(nowMs = this._now()) {
180
+ return this.list().filter((r) => {
181
+ if (!r.enabled) return false;
182
+ if (r.trigger.kind === "cron") {
183
+ const from = r.lastFiredAt || r.createdAt || 0;
184
+ const next = nextCronTime(r.trigger.cron, from);
185
+ return next != null && next <= nowMs;
186
+ }
187
+ if (r.trigger.kind === "once") {
188
+ return !r.lastFiredAt && r.trigger.at <= nowMs;
189
+ }
190
+ return false; // webhook/github fire through other paths
191
+ });
192
+ }
193
+
194
+ /** GitHub-triggered routines the driver should poll. */
195
+ githubRoutines() {
196
+ return this.list().filter((r) => r.enabled && r.trigger.kind === "github");
197
+ }
198
+
199
+ // ── run history ──────────────────────────────────────────────────────────
200
+
201
+ _appendRun(record) {
202
+ this._ensureDir();
203
+ fs.appendFileSync(this._runsFile(), JSON.stringify(record) + "\n", "utf-8");
204
+ }
205
+
206
+ recordRunStart(routineId, meta = {}) {
207
+ const runId = `run-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
208
+ this._appendRun({
209
+ type: "start",
210
+ runId,
211
+ routineId,
212
+ trigger: meta.trigger || null,
213
+ pid: meta.pid || null,
214
+ startedAt: this._now(),
215
+ });
216
+ return runId;
217
+ }
218
+
219
+ recordRunEnd(runId, result = {}) {
220
+ this._appendRun({
221
+ type: "end",
222
+ runId,
223
+ endedAt: this._now(),
224
+ status: result.status || (result.exitCode === 0 ? "ok" : "failed"),
225
+ exitCode: result.exitCode ?? null,
226
+ summary: String(result.summary || "").slice(0, 500),
227
+ usage: result.usage || null,
228
+ costUsd: Number.isFinite(result.costUsd) ? result.costUsd : null,
229
+ durationMs: result.durationMs ?? null,
230
+ });
231
+ }
232
+
233
+ /** Merged run rows (start+end), newest first. */
234
+ listRuns({ routineId = null, limit = 20 } = {}) {
235
+ let lines = [];
236
+ try {
237
+ lines = fs
238
+ .readFileSync(this._runsFile(), "utf-8")
239
+ .split("\n")
240
+ .filter((l) => l.trim());
241
+ } catch {
242
+ return [];
243
+ }
244
+ const runs = new Map();
245
+ for (const line of lines) {
246
+ let rec;
247
+ try {
248
+ rec = JSON.parse(line);
249
+ } catch {
250
+ continue; // per-row resilience
251
+ }
252
+ if (rec.type === "start") {
253
+ runs.set(rec.runId, { ...rec, status: "running" });
254
+ } else if (rec.type === "end") {
255
+ const prev = runs.get(rec.runId) || { runId: rec.runId };
256
+ runs.set(rec.runId, { ...prev, ...rec, type: undefined });
257
+ }
258
+ }
259
+ return [...runs.values()]
260
+ .filter((r) => !routineId || r.routineId === routineId)
261
+ .sort((a, b) => (b.startedAt || 0) - (a.startedAt || 0))
262
+ .slice(0, limit)
263
+ .map(({ type: _t, ...rest }) => rest);
264
+ }
265
+
266
+ /** Per-routine aggregate for `cc routine list`: runs, ok/failed, cost. */
267
+ summarize(routineId) {
268
+ const runs = this.listRuns({ routineId, limit: 1000 });
269
+ const done = runs.filter((r) => r.status && r.status !== "running");
270
+ return {
271
+ totalRuns: runs.length,
272
+ ok: done.filter((r) => r.status === "ok").length,
273
+ failed: done.filter((r) => r.status === "failed").length,
274
+ running: runs.filter((r) => r.status === "running").length,
275
+ costUsd: done.reduce(
276
+ (sum, r) => sum + (Number.isFinite(r.costUsd) ? r.costUsd : 0),
277
+ 0,
278
+ ),
279
+ lastRun: runs[0] || null,
280
+ };
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Fire one routine: record history, run the agent, persist the log, update
286
+ * lastFiredAt (and disable a fired `once`). `runAgent({prompt})` must resolve
287
+ * `{ exitCode, output, usage?, costUsd?, pid? }`.
288
+ */
289
+ export async function fireRoutine(store, routine, runAgent, meta = {}) {
290
+ const startedAt = store._now();
291
+ const runId = store.recordRunStart(routine.id, {
292
+ trigger: meta.trigger || routine.trigger.kind,
293
+ });
294
+ let result;
295
+ try {
296
+ result = await runAgent({ prompt: routine.prompt, routine, runId });
297
+ } catch (err) {
298
+ result = { exitCode: -1, output: `runner error: ${err.message}` };
299
+ }
300
+ const output = String(result.output || "");
301
+ try {
302
+ store._ensureDir();
303
+ fs.writeFileSync(store.logFile(runId), output, "utf-8");
304
+ } catch {
305
+ /* log persistence is best-effort */
306
+ }
307
+ store.recordRunEnd(runId, {
308
+ exitCode: result.exitCode,
309
+ status: result.exitCode === 0 ? "ok" : "failed",
310
+ summary: output.trim().split("\n").slice(-3).join(" ⏎ "),
311
+ usage: result.usage || null,
312
+ costUsd: result.costUsd,
313
+ durationMs: store._now() - startedAt,
314
+ });
315
+ store.update(routine.id, {
316
+ lastFiredAt: startedAt,
317
+ ...(routine.trigger.kind === "once" ? { enabled: false } : {}),
318
+ });
319
+ return runId;
320
+ }
321
+
322
+ /**
323
+ * Poll a github-triggered routine: fetch the repo's latest events via the
324
+ * injected `fetchEvents(repo)` (production: `gh api repos/<repo>/events`),
325
+ * fire when new events of the requested types appeared. Returns fired runIds.
326
+ */
327
+ export async function pollGithubRoutine(
328
+ store,
329
+ routine,
330
+ { fetchEvents, runAgent },
331
+ ) {
332
+ const events = (await fetchEvents(routine.trigger.repo)) || [];
333
+ const wanted = Array.isArray(routine.trigger.events)
334
+ ? routine.trigger.events
335
+ : null;
336
+ const fresh = events.filter((e) => {
337
+ if (wanted && wanted.length > 0 && !wanted.includes(e.type)) return false;
338
+ return (
339
+ !routine.lastSeenGithubEventId ||
340
+ String(e.id) > String(routine.lastSeenGithubEventId)
341
+ );
342
+ });
343
+ const newestId = events.length ? String(events[0].id) : null;
344
+ if (newestId) {
345
+ store.update(routine.id, { lastSeenGithubEventId: newestId });
346
+ }
347
+ if (fresh.length === 0) return [];
348
+ const runId = await fireRoutine(store, routine, runAgent, {
349
+ trigger: `github:${fresh.map((e) => e.type).join(",")}`,
350
+ });
351
+ return [runId];
352
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Session-store write guard — transcript tamper protection (agent tool layer).
3
+ *
4
+ * Session transcripts under `~/.chainlesschain/sessions` are hash-chained and
5
+ * tamper-evident (see harness/transcript-integrity.js). An agent writing into
6
+ * that directory could rewrite the very history that `cc session verify`,
7
+ * resume, and audit trust — so write tools confirm first, exactly like the
8
+ * sensitive-file guard. Pure path logic lives here so it is unit-testable
9
+ * without the agent runtime.
10
+ */
11
+
12
+ import { resolve, relative, isAbsolute, join } from "node:path";
13
+ import { getHomeDir } from "./paths.js";
14
+
15
+ export function sessionStoreDir() {
16
+ return join(getHomeDir(), "sessions");
17
+ }
18
+
19
+ /**
20
+ * True when targetPath (relative paths resolve against opts.cwd) lands inside
21
+ * the session transcript store. path.win32.relative compares
22
+ * case-insensitively, so mixed-case Windows paths are handled.
23
+ */
24
+ export function isWithinSessionStore(targetPath, opts = {}) {
25
+ const p = String(targetPath || "");
26
+ if (!p) return false;
27
+ const abs = resolve(opts.cwd || process.cwd(), p);
28
+ const rel = relative(resolve(sessionStoreDir()), abs);
29
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
30
+ }
31
+
32
+ /**
33
+ * @param {string} targetPath path as the tool received it (rel or abs)
34
+ * @returns {string|null} human reason when the path is a transcript-store
35
+ * write, null otherwise
36
+ */
37
+ export function sessionStorePathReason(targetPath, opts = {}) {
38
+ if (!isWithinSessionStore(targetPath, opts)) return null;
39
+ return "session transcript store — transcripts are hash-chained and tamper-evident; direct edits break the chain";
40
+ }