chainlesschain 0.162.154 → 0.162.156

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 (205) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-CIpoXyVI.js → AIOps-DjBXlypQ.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-Ch_QXIPk.js → ActionButton-Cv9er9bp.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-CvaBYShv.js → Analytics-CLKDjSNs.js} +3 -3
  7. package/src/assets/web-panel/assets/AppLayout-CfyZNzmJ.js +9 -0
  8. package/src/assets/web-panel/assets/{Audit-Bng8eHCW.js → Audit-CXafD178.js} +1 -1
  9. package/src/assets/web-panel/assets/BackgroundAgents-CP5c-GSd.js +1 -0
  10. package/src/assets/web-panel/assets/BackgroundAgents-Dq2340Kl.css +1 -0
  11. package/src/assets/web-panel/assets/{Backup-DvBs8-lm.js → Backup-BbYLQfyt.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-DKwajm0g.js → BaseInput-Bqe0MwZL.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-BoGpZ3uO.js → Chat-jNA_yUPH.js} +5 -5
  14. package/src/assets/web-panel/assets/{ChatBubbleRenderer-mDQl_PXO.js → ChatBubbleRenderer-BOFLF01s.js} +1 -1
  15. package/src/assets/web-panel/assets/{Checkbox-BMJEmxFQ.js → Checkbox-Dlzz-zD1.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-Bsoag5CV.js → Codegen-BAsDQjGf.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-CDjH9-sp.js → Col-Ju0HrAuS.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-Cg2QHPQ6.js → Community-DynJd43Z.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-BU2BaqP-.js → Compact-CabbZVww.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-D6ZYMCqZ.js → Compliance-DW4BmAoh.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-DaZ8VW-I.js → Cowork-Bk1S_7X-.js} +2 -2
  22. package/src/assets/web-panel/assets/{Cron-DQY1RH9v.js → Cron-By4sh_NM.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-BVR28vk9.js → Crosschain-DdZdbS1C.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-N44KvTP-.js → DID-CkL-sQyS.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-Bjhlx6wS.js → Dashboard-g_mOpumg.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-CpLYxpy1.js → Dropdown-DtHyE7Ao.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-srEITSPI.js → EmailListRenderer-Kpvov5Wr.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-juOC_fX2.js → FamilyGuardDashboard-C4kZoePR.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-nRaQtaXW.js → Federation-BAwWoK2N.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-DflhTj09.js → FormItemContext-DeI0i5f8.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-DulI_3NI.js → GenericCardRenderer-CkcKRtos.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-BxOPefXn.js → Git-kX6jjI9y.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-C6G_Wnwi.js → Governance-BXJCBm6x.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-BnFYa2BF.js → Inference-ujD2JSKo.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-TqQLEabm.js → KnowledgeGraph-3Dnuf1Wh.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-BH4AVh7s.js → Logs-lVAZCtUB.js} +2 -2
  37. package/src/assets/web-panel/assets/{Marketplace-DoWLTaEE.js → Marketplace-qnzNPm-A.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-D6yC7OG_.js → McpTools-CqjzefRU.js} +5 -5
  39. package/src/assets/web-panel/assets/{Memory-DLCeHj21.js → Memory-BFiPAxk6.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-BZBE0GRl.js → MobileBridge-D0O43VwF.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-DJA15G1J.js → MobileProjects-B3now48t.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-CyTKJDHn.js → Mtc-DybhtaCS.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-DZKjgEVc.js → MtcAudit-D7MmY4F0.js} +5 -5
  44. package/src/assets/web-panel/assets/{Multisig-2rMPNhSO.js → Multisig-wZvGJnB8.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-VeMk4Fx-.js → NLProgramming-BNPpgfvn.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-jr2GHMhh.js → Notes-B6FS3KBj.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-WN9VZyFb.js → NotificationSettings-CK0yla9U.js} +1 -1
  48. package/src/assets/web-panel/assets/OrderTableRenderer-CXfPHIpy.js +1 -0
  49. package/src/assets/web-panel/assets/{Organization-DdjVHuzT.js → Organization-geqgwN1T.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-l8vBsGGq.js → Overflow-BWJYZFbC.js} +1 -1
  51. package/src/assets/web-panel/assets/{OverrideContext-BlgL9440.js → OverrideContext-C0w0P4A3.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-CVQDHHwq.js → P2P-DnU1505F.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-CTsNh8qE.js → PdhVaultBrowser-BGY74KnC.js} +3 -3
  54. package/src/assets/web-panel/assets/{Permissions-DLW87uyK.js → Permissions-CgArrWtQ.js} +3 -3
  55. package/src/assets/web-panel/assets/{PersonalDataHub-N8OvASTx.js → PersonalDataHub-C2vYE61a.js} +2 -2
  56. package/src/assets/web-panel/assets/{Pipeline-BPZmdlz2.js → Pipeline-CBYLI4yW.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-MFZw_GNh.js → Privacy-B-PdW9aN.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-zW0pEsfU.js → ProjectInit-CuWQegbV.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-CppVHr_o.js → ProjectSettings-4d32m_cS.js} +2 -2
  60. package/src/assets/web-panel/assets/{Projects-BTglkJO2.js → Projects-VLA7ttwd.js} +1 -1
  61. package/src/assets/web-panel/assets/{Providers-CPHZ6JKT.js → Providers-B_Izb_oa.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-DO4klNZ2.js → QrScannerModal-C6B_ndXV.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-B37TTqUa.js → QuickAsk-C6tavL-y.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-n7KIfK7y.js → Recommend-CF-LaZ6x.js} +1 -1
  65. package/src/assets/web-panel/assets/{RemoteSession-SjVoS9mm.js → RemoteSession-DBqTTQ9_.js} +2 -2
  66. package/src/assets/web-panel/assets/{Reputation-Dt6RTeHi.js → Reputation-B01FALUi.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-Dty6jz5V.js → Row-DO0fd48A.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-DDLhqw5j.js → RssFeed-BV5LDzFR.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-DrYbXi-W.js → Search-CTti4_MP.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-Fc9Z6fbB.js → Security-DwSrWRCw.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-BAhPgjSM.js → Services-BEf1QyM4.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-8JF7EPfE.js → Skeleton-LZXTpovy.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-DL39dY0t.js → Skills-SKgc0Zug.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-CSvjcATc.js → Sla-C_QVOtkN.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-Demx7Gnx.js → SpeechSettings-v5TW2V-X.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-CKLnRWJr.js → SyncSettings-CQPjHdqU.js} +2 -2
  77. package/src/assets/web-panel/assets/Tasks-DCd-uYcI.js +1 -0
  78. package/src/assets/web-panel/assets/{Templates-AjQI5rxD.js → Templates-62VDIjtj.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-dbtEh1yR.js → Tenant-BnFQ0pNy.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-DX-9jjfk.js → Terminal-D3v0MGSj.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-C11EDZRt.js → TimelineRenderer-BI1Ka-cu.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-GiA9fiZn.js → Tokens-BwX1jXYP.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-BPcnclJn.js → Trigger-CSA8TqNx.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-C-DVY8nq.js → Trust-Bn3IjCx7.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-DxodSwME.js → UkeySign-pUbOAqNi.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-D_WV0O8O.js → VideoEditing-Db2RercF.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-DSeNlVSU.js → Wallet-CpR1BVAl.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-Bwuuvpz1.js → WebAuthn-Yxtkn7Zp.js} +5 -5
  89. package/src/assets/web-panel/assets/{WorkflowEditor-8M96EU1N.js → WorkflowEditor-CYLe9a2E.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-D5S6SmLm.js → chat-gsOGdak0.js} +1 -1
  91. package/src/assets/web-panel/assets/{collapseMotion-DVfhbsdP.js → collapseMotion-CqM1QOwM.js} +1 -1
  92. package/src/assets/web-panel/assets/{colors-5fbnGkAP.js → colors-D9fN299a.js} +1 -1
  93. package/src/assets/web-panel/assets/{compact-item-DaSuuMlW.js → compact-item-vkd7s-ay.js} +1 -1
  94. package/src/assets/web-panel/assets/{createContext-BNOHcXAF.js → createContext-C7YkbMyy.js} +1 -1
  95. package/src/assets/web-panel/assets/devWarning-DEcR2h6k.js +1 -0
  96. package/src/assets/web-panel/assets/{echarts-CL0F2SnX.js → echarts-C61Vn3mP.js} +1 -1
  97. package/src/assets/web-panel/assets/{hasIn-DONwZUra.js → hasIn-ig7reRSU.js} +1 -1
  98. package/src/assets/web-panel/assets/{icons-BsDmGpcT.js → icons-DEmcnxBf.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BJp4kRjw.js → index-9TxdMYju.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-MOgO5Yvx.js → index-B7QRpKif.js} +1 -1
  101. package/src/assets/web-panel/assets/index-BEibanjg.js +1 -0
  102. package/src/assets/web-panel/assets/{index-CAYmc5Nh.js → index-BHHN_KTS.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BcWMZqb4.js → index-BMw48d19.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-B_yDD9Lm.js → index-BYe9HRSt.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-xo2f4DZR.js → index-BfpVcXKa.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DbdQBn4n.js → index-BhftELHI.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Dd7xwGcD.js → index-BkP17Z95.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-RkInlwV0.js → index-BpGlLbRv.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DM3Ug61e.js → index-Bt-9ZhIu.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CrkCFohi.js → index-BvDEukXe.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-_6S41ikV.js → index-C7AY39sg.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-uR5KTNZA.js → index-C8HZMygt.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-5_fj5gVd.js → index-CAkz1f1r.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BLzMuz38.js → index-CDWTycrg.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BEO5f7Vf.js → index-CGzGe5tv.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CEzd5m5a.js → index-Co2NYyew.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BcqOK7Lv.js → index-CspGymmg.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DP2-3A_j.js → index-D2yhtk6a.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-LRIt-AFl.js → index-D6s_kbjt.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-B4L3gsNA.js → index-DFM1oNe1.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-KwV9g_cf.js → index-DFfxF7Vt.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BK2N5yVW.js → index-DPguq3X7.js} +4 -4
  123. package/src/assets/web-panel/assets/{index-Cf2OCa8q.js → index-DQ8U7kzC.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-D7gs_3Ck.js → index-DZAxCnjS.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-PsMErAB1.js → index-DhdXbsXX.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CTZeOnzO.js → index-DuwbKHoS.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-Dm5A-MN5.js → index-DvCJXmrx.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-vYtprlNs.js → index-Dy_TKZ9h.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DHx2lhHW.js → index-FzGr6CVk.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Cn5LtyIH.js → index-PkH39qsp.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CmUOjyL6.js → index-SDrcQXO1.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-CztESwB4.js → index-W52sHRdr.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-CSfYJqXt.js → index-bVbgYBhv.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-BrAJSu-v.js → index-gj8kxcAi.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-CLXf-8ek.js → index-gpurplyq.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-ClGqO-2X.js → index-nX8YE6cj.js} +1 -1
  137. package/src/assets/web-panel/assets/index-z_25tU3p.js +1 -0
  138. package/src/assets/web-panel/assets/{initDefaultProps-CxwLynp9.js → initDefaultProps-BrgfVRg9.js} +1 -1
  139. package/src/assets/web-panel/assets/{motion-XbPvGJbW.js → motion-D_3xmwac.js} +1 -1
  140. package/src/assets/web-panel/assets/{move-D1u5pcgS.js → move-DDDJdlxw.js} +1 -1
  141. package/src/assets/web-panel/assets/{mtc-parser-BDxhTBMH.js → mtc-parser-BK2uEWwn.js} +1 -1
  142. package/src/assets/web-panel/assets/{omit-EtIFGg5k.js → omit-BPHKbDcf.js} +1 -1
  143. package/src/assets/web-panel/assets/{pickAttrs-D33sWb0l.js → pickAttrs-LNnTscuz.js} +1 -1
  144. package/src/assets/web-panel/assets/{placementArrow-BwFRzaGo.js → placementArrow-CvmTZll5.js} +1 -1
  145. package/src/assets/web-panel/assets/{responsiveObserve-CBk2WqvQ.js → responsiveObserve-eBj7qi2J.js} +1 -1
  146. package/src/assets/web-panel/assets/{slide-B52HtrKn.js → slide-D68hHkrM.js} +1 -1
  147. package/src/assets/web-panel/assets/{statusUtils-D4qxqWnH.js → statusUtils-daTlaeo0.js} +1 -1
  148. package/src/assets/web-panel/assets/{styleChecker-BxaCFlOL.js → styleChecker-DtyKMtHB.js} +1 -1
  149. package/src/assets/web-panel/assets/useFlexGapSupport-ekFwFs9h.js +1 -0
  150. package/src/assets/web-panel/assets/{useFs-C6VAU1_6.js → useFs-jRaSEoF4.js} +1 -1
  151. package/src/assets/web-panel/assets/{useMergedState-Dc9sO2d7.js → useMergedState-DXbJa_zE.js} +1 -1
  152. package/src/assets/web-panel/assets/{usePersonalDataHub-rEG6oXG8.js → usePersonalDataHub-DD5nGzjA.js} +1 -1
  153. package/src/assets/web-panel/assets/{useRefs-CAeFRzPf.js → useRefs-BrCfMLMc.js} +1 -1
  154. package/src/assets/web-panel/assets/{useState-LwExUYDO.js → useState-CnxtbLoC.js} +1 -1
  155. package/src/assets/web-panel/assets/{vendor-BMxUs6UX.js → vendor-Bn1JzzAn.js} +1 -1
  156. package/src/assets/web-panel/assets/{vnode-DOTafkEi.js → vnode-KcgLUiom.js} +1 -1
  157. package/src/assets/web-panel/assets/{zoom-BuwwN5K2.js → zoom-Cyk2PQ-s.js} +1 -1
  158. package/src/assets/web-panel/index.html +3 -3
  159. package/src/command-manifest.json +50 -1
  160. package/src/commands/agenda.js +228 -0
  161. package/src/commands/agent.js +23 -2
  162. package/src/commands/agents.js +62 -1
  163. package/src/commands/auto-mode.js +87 -0
  164. package/src/commands/background-session.js +499 -0
  165. package/src/commands/batch.js +345 -0
  166. package/src/commands/command.js +4 -1
  167. package/src/commands/permissions.js +49 -0
  168. package/src/commands/remote-control.js +407 -0
  169. package/src/data/changelog.json +12 -2
  170. package/src/gateways/ws/background-agent-protocol.js +337 -0
  171. package/src/gateways/ws/message-dispatcher.js +10 -0
  172. package/src/gateways/ws/remote-session-protocol.js +102 -1
  173. package/src/gateways/ws/ws-server.js +61 -0
  174. package/src/harness/mcp-client.js +8 -0
  175. package/src/index.js +22 -0
  176. package/src/lib/agent-batch.js +235 -0
  177. package/src/lib/agent-notify.js +66 -0
  178. package/src/lib/agent-schedule-store.js +330 -0
  179. package/src/lib/auto-mode-config.js +396 -0
  180. package/src/lib/background-agent-supervisor.js +202 -17
  181. package/src/lib/background-session-transport.js +315 -0
  182. package/src/lib/permission-denial-store.js +159 -0
  183. package/src/lib/remote-approval-bridge.js +360 -0
  184. package/src/lib/remote-control.js +270 -0
  185. package/src/lib/repl-denials.js +26 -0
  186. package/src/lib/shell-approval.js +4 -0
  187. package/src/lib/ws-rpc-client.js +150 -0
  188. package/src/repl/agent-repl.js +208 -16
  189. package/src/repl/permission-tier.js +31 -0
  190. package/src/runtime/agent-core.js +161 -0
  191. package/src/runtime/coding-agent-contract-shared.cjs +104 -0
  192. package/src/runtime/coding-agent-policy.cjs +28 -0
  193. package/src/runtime/headless-runner.js +126 -8
  194. package/src/runtime/headless-stream.js +87 -1
  195. package/src/runtime/mcp-config.js +11 -0
  196. package/src/runtime/mcp-tool-search.js +0 -0
  197. package/src/runtime/policies/agent-policy.js +17 -0
  198. package/src/workers/background-agent-worker.js +218 -37
  199. package/src/assets/web-panel/assets/AppLayout-B6Q6olcr.js +0 -9
  200. package/src/assets/web-panel/assets/OrderTableRenderer-CjNO-e9T.js +0 -1
  201. package/src/assets/web-panel/assets/Tasks-mhITRxjv.js +0 -1
  202. package/src/assets/web-panel/assets/devWarning-K_woVElh.js +0 -1
  203. package/src/assets/web-panel/assets/index-3ILvaBpo.js +0 -1
  204. package/src/assets/web-panel/assets/index-JpB-FoZq.js +0 -1
  205. package/src/assets/web-panel/assets/useFlexGapSupport-B1lWlqXN.js +0 -1
@@ -0,0 +1,235 @@
1
+ /**
2
+ * agent-batch — dynamic worktree batch core (gap-analysis 第四阶段 #4).
3
+ *
4
+ * Takes a large task already decomposed into independent UNITS and runs each in
5
+ * its OWN git worktree, in parallel (bounded concurrency), then aggregates the
6
+ * outcome: per-unit agent status, test result, diff stat, and — on integration
7
+ * — a merge-conflict preview against base. This is the `/batch` shape from
8
+ * Claude Code, built on the same worktree isolation `cc team --worktree` uses.
9
+ *
10
+ * Pure over injected deps (worktree lifecycle, agent/test spawns, git) so the
11
+ * whole fan-out + aggregation is unit-testable without a real repo or LLM.
12
+ *
13
+ * A `unit` is `{ key, prompt, test? }`. Concurrency is a simple worker pool.
14
+ * Integration is SEQUENTIAL (like TeamWorktreeCoordinator.integrate) so a later
15
+ * branch that conflicts with an already-merged one is reported, not clobbered.
16
+ */
17
+
18
+ /** Run `items` through `worker` with at most `limit` in flight; preserves order. */
19
+ async function mapPool(items, limit, worker) {
20
+ const results = new Array(items.length);
21
+ let next = 0;
22
+ const runners = new Array(Math.max(1, Math.min(limit, items.length)))
23
+ .fill(0)
24
+ .map(async () => {
25
+ while (true) {
26
+ const i = next++;
27
+ if (i >= items.length) return;
28
+ results[i] = await worker(items[i], i);
29
+ }
30
+ });
31
+ await Promise.all(runners);
32
+ return results;
33
+ }
34
+
35
+ function normalizeUnits(units) {
36
+ if (!Array.isArray(units) || units.length === 0) {
37
+ throw new Error("batch requires a non-empty `units` array");
38
+ }
39
+ const seen = new Set();
40
+ return units.map((u, i) => {
41
+ const key = String(u.key || `unit-${i + 1}`);
42
+ if (seen.has(key)) throw new Error(`duplicate unit key "${key}"`);
43
+ seen.add(key);
44
+ if (!u.prompt || typeof u.prompt !== "string") {
45
+ throw new Error(`unit "${key}" has no prompt`);
46
+ }
47
+ return { key, prompt: u.prompt, test: u.test || null };
48
+ });
49
+ }
50
+
51
+ /**
52
+ * @param {object} opts
53
+ * @param {Array<{key,prompt,test?}>} opts.units
54
+ * @param {number} [opts.concurrency=4]
55
+ * @param {string} [opts.test] default test command for units without one
56
+ * @param {boolean} [opts.merge] merge clean branches back to base
57
+ * @param {(msg:object)=>void} [opts.onEvent] progress callback
58
+ * @param {object} deps injected effectful ops (see command wiring)
59
+ * @returns {Promise<{units:Array, summary:object}>}
60
+ */
61
+ export async function runBatch(opts, deps) {
62
+ const units = normalizeUnits(opts.units);
63
+ const concurrency = Math.max(1, Number(opts.concurrency) || 4);
64
+ const onEvent = opts.onEvent || (() => {});
65
+ const {
66
+ createWorktree,
67
+ removeWorktree,
68
+ runAgent,
69
+ runTest,
70
+ diffStat,
71
+ commit,
72
+ previewMerge,
73
+ mergeBranch,
74
+ branchFor = (key) => `batch/${key}`,
75
+ } = deps;
76
+
77
+ onEvent({ type: "batch:start", units: units.length, concurrency });
78
+
79
+ const results = await mapPool(units, concurrency, async (unit) => {
80
+ const branch = branchFor(unit.key);
81
+ const record = {
82
+ key: unit.key,
83
+ branch,
84
+ status: "pending",
85
+ filesChanged: 0,
86
+ insertions: 0,
87
+ deletions: 0,
88
+ testCommand: unit.test || opts.test || null,
89
+ testPassed: null,
90
+ committed: false,
91
+ error: null,
92
+ };
93
+ let worktreePath = null;
94
+ try {
95
+ worktreePath = createWorktree(unit.key, branch);
96
+ onEvent({ type: "unit:start", key: unit.key, branch });
97
+ await runAgent(unit.prompt, worktreePath);
98
+
99
+ const stat = diffStat(worktreePath);
100
+ record.filesChanged = stat.filesChanged;
101
+ record.insertions = stat.insertions;
102
+ record.deletions = stat.deletions;
103
+
104
+ const testCmd = unit.test || opts.test || null;
105
+ if (testCmd) {
106
+ try {
107
+ await runTest(testCmd, worktreePath);
108
+ record.testPassed = true;
109
+ } catch (err) {
110
+ record.testPassed = false;
111
+ record.testError = err.message;
112
+ }
113
+ }
114
+
115
+ if (stat.filesChanged > 0) {
116
+ record.committed = commit(worktreePath, `batch: ${unit.key}`);
117
+ }
118
+ // A unit whose tests FAILED is reported failed even though its worktree
119
+ // committed — integration below skips non-passing units so a red unit
120
+ // never merges into base.
121
+ record.status =
122
+ record.testPassed === false
123
+ ? "test-failed"
124
+ : record.filesChanged === 0
125
+ ? "no-changes"
126
+ : "done";
127
+ } catch (err) {
128
+ record.status = "error";
129
+ record.error = err.message;
130
+ }
131
+ onEvent({
132
+ type: "unit:done",
133
+ key: unit.key,
134
+ status: record.status,
135
+ testPassed: record.testPassed,
136
+ filesChanged: record.filesChanged,
137
+ });
138
+ return { record, worktreePath };
139
+ });
140
+
141
+ // Sequential integration: only units that DID change, committed, and did not
142
+ // fail tests are eligible. Preview each; merge clean ones when --merge.
143
+ for (const { record } of results) {
144
+ if (!record.committed || record.status === "test-failed") {
145
+ record.integration = { eligible: false, reason: record.status };
146
+ continue;
147
+ }
148
+ let preview;
149
+ try {
150
+ preview = previewMerge(record.branch);
151
+ } catch (err) {
152
+ record.integration = {
153
+ eligible: true,
154
+ clean: false,
155
+ merged: false,
156
+ conflicts: [],
157
+ error: err.message,
158
+ };
159
+ continue;
160
+ }
161
+ const clean = preview?.success === true;
162
+ const conflicts = preview?.conflicts || [];
163
+ let merged = false;
164
+ if (clean && opts.merge) {
165
+ let mergeResult;
166
+ try {
167
+ mergeResult = mergeBranch(record.branch, `Merge batch ${record.key}`);
168
+ } catch (err) {
169
+ mergeResult = { success: false, message: err.message };
170
+ }
171
+ merged = mergeResult?.success === true;
172
+ if (!merged && (!conflicts || conflicts.length === 0)) {
173
+ record.integration = {
174
+ eligible: true,
175
+ clean,
176
+ merged,
177
+ conflicts: mergeResult?.conflicts || [],
178
+ error: mergeResult?.message || null,
179
+ };
180
+ continue;
181
+ }
182
+ }
183
+ record.integration = { eligible: true, clean, merged, conflicts };
184
+ onEvent({
185
+ type: "unit:integrated",
186
+ key: record.key,
187
+ clean,
188
+ merged,
189
+ conflicts: conflicts.length,
190
+ });
191
+ }
192
+
193
+ // Best-effort teardown of every worktree we created (branches persist for the
194
+ // user to inspect/merge later unless already merged).
195
+ if (typeof removeWorktree === "function") {
196
+ for (const { record, worktreePath } of results) {
197
+ if (!worktreePath) continue;
198
+ try {
199
+ removeWorktree(worktreePath, {
200
+ deleteBranch: record.integration?.merged === true,
201
+ });
202
+ } catch {
203
+ // best-effort — a leftover worktree is recoverable via `git worktree`
204
+ }
205
+ }
206
+ }
207
+
208
+ const records = results.map((r) => r.record);
209
+ const summary = summarize(records);
210
+ onEvent({ type: "batch:done", ...summary });
211
+ return { units: records, summary };
212
+ }
213
+
214
+ function summarize(records) {
215
+ const summary = {
216
+ total: records.length,
217
+ done: 0,
218
+ testFailed: 0,
219
+ noChanges: 0,
220
+ errored: 0,
221
+ merged: 0,
222
+ conflicted: 0,
223
+ };
224
+ for (const r of records) {
225
+ if (r.status === "done") summary.done += 1;
226
+ else if (r.status === "test-failed") summary.testFailed += 1;
227
+ else if (r.status === "no-changes") summary.noChanges += 1;
228
+ else if (r.status === "error") summary.errored += 1;
229
+ if (r.integration?.merged) summary.merged += 1;
230
+ if (r.integration?.conflicts?.length) summary.conflicted += 1;
231
+ }
232
+ return summary;
233
+ }
234
+
235
+ export const _internal = { mapPool, normalizeUnits, summarize };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * agent-notify — shared backing for the `notify` agent tool + `cc agenda`
3
+ * monitor firing (gap-analysis 第四阶段 #3).
4
+ *
5
+ * Fans a notification out over the user's configured multi-channel notifiers
6
+ * (Telegram / WeCom / DingTalk / Feishu, via NotificationManager.fromEnv) and,
7
+ * when a paired-device push descriptor exists, the mobile push bridge. Pure
8
+ * over injected deps so it is unit-testable without any network.
9
+ */
10
+
11
+ import { NotificationManager } from "./notifiers/index.js";
12
+
13
+ /**
14
+ * @param {{title:string, body?:string, level?:"info"|"success"|"failure"}} msg
15
+ * @param {{env?:object, manager?:object}} [deps]
16
+ * @returns {Promise<{delivered:string[], failed:string[], channels:number, results:Array}>}
17
+ */
18
+ export async function sendAgentNotification(msg, deps = {}) {
19
+ const title = String(msg?.title || "").trim();
20
+ if (!title) throw new Error("notify requires a title");
21
+ const body = msg?.body ? String(msg.body) : "";
22
+ const level = ["info", "success", "failure"].includes(msg?.level)
23
+ ? msg.level
24
+ : "info";
25
+
26
+ const manager =
27
+ deps.manager ||
28
+ NotificationManager.fromEnv({ env: deps.env || process.env });
29
+ if (!manager.isConfigured) {
30
+ return {
31
+ delivered: [],
32
+ failed: [],
33
+ channels: 0,
34
+ results: [],
35
+ note: "No notification channels configured (set TELEGRAM_BOT_TOKEN / WECOM_WEBHOOK_URL / DINGTALK_WEBHOOK_URL / FEISHU_WEBHOOK_URL).",
36
+ };
37
+ }
38
+
39
+ const summary = {
40
+ taskId: msg?.taskId || null,
41
+ description: title,
42
+ detail: body,
43
+ message: body || title,
44
+ title,
45
+ };
46
+ const method =
47
+ level === "success"
48
+ ? "notifySuccess"
49
+ : level === "failure"
50
+ ? "notifyFailure"
51
+ : "notifyStart";
52
+ const results = await manager[method](summary);
53
+
54
+ const delivered = [];
55
+ const failed = [];
56
+ for (const r of results || []) {
57
+ if (r && r.ok !== false) delivered.push(r.channel);
58
+ else failed.push(r?.channel || "unknown");
59
+ }
60
+ return {
61
+ delivered,
62
+ failed,
63
+ channels: (manager.activeChannels || []).length,
64
+ results: results || [],
65
+ };
66
+ }
@@ -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
+ }