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,219 @@
1
+ /**
2
+ * artifact WS protocol — exposes the agent-published deliverable store
3
+ * (lib/artifact-store.js, P1 #10) to web-panel / IDE clients so an
4
+ * "Artifacts" panel can list deliverables, preview them (text inline /
5
+ * images as base64), and manage retention.
6
+ *
7
+ * Routes (request → reply):
8
+ * artifact-list {session?, kind?} → {type:"artifact-list", artifacts}
9
+ * artifact-show {artifactId} → {type:"artifact-show", artifact}
10
+ * artifact-content {artifactId, maxBytes?} → {type:"artifact-content", artifactId,
11
+ * mime, encoding, content, size,
12
+ * truncated, previewable}
13
+ * artifact-remove {artifactId} → {type:"artifact-remove", removed, found}
14
+ * artifact-clean {} → {type:"artifact-clean", removed}
15
+ *
16
+ * Preview policy: text mimes stream as utf8 (capped, `truncated` flagged);
17
+ * images as base64 (hard cap — an oversized image reports previewable:false
18
+ * instead of flooding the socket); everything else is metadata-only
19
+ * (previewable:false, the user has `cc artifacts open <id>` locally).
20
+ *
21
+ * The stored-copy filename comes from index.jsonl; serving is refused if it
22
+ * is not a plain basename, so a hand-tampered index row can never turn the
23
+ * content route into an arbitrary-file read.
24
+ */
25
+
26
+ import path from "node:path";
27
+ import { readFileSync, statSync } from "node:fs";
28
+
29
+ /** Text preview cap (utf8 chars ≈ bytes for the common case). */
30
+ export const TEXT_PREVIEW_CAP = 256 * 1024;
31
+ /** Image preview cap — beyond this the panel gets metadata only. */
32
+ export const IMAGE_PREVIEW_CAP = 8 * 1024 * 1024;
33
+
34
+ const TEXT_APPLICATION_MIMES = new Set([
35
+ "application/json",
36
+ "application/yaml",
37
+ "application/xml",
38
+ ]);
39
+
40
+ async function loadStore() {
41
+ const { ArtifactStore } = await import("../../lib/artifact-store.js");
42
+ return new ArtifactStore();
43
+ }
44
+
45
+ function sendError(server, id, ws, code, message) {
46
+ server._send(ws, { id, type: "error", code, message });
47
+ }
48
+
49
+ /** Classify how (whether) an artifact can be previewed inline. */
50
+ export function previewKindForMime(mime) {
51
+ const m = String(mime || "").toLowerCase();
52
+ if (m.startsWith("text/") || TEXT_APPLICATION_MIMES.has(m)) return "text";
53
+ if (m.startsWith("image/")) return "image";
54
+ return "none";
55
+ }
56
+
57
+ export async function handleArtifactList(server, id, ws, message) {
58
+ try {
59
+ const store = await loadStore();
60
+ let artifacts = store.list({ sessionId: message.session || undefined });
61
+ if (message.kind) {
62
+ artifacts = artifacts.filter((e) => e.kind === String(message.kind));
63
+ }
64
+ server._send(ws, { id, type: "artifact-list", artifacts });
65
+ } catch (err) {
66
+ sendError(server, id, ws, "ARTIFACT_LIST_FAILED", err.message);
67
+ }
68
+ }
69
+
70
+ export async function handleArtifactShow(server, id, ws, message) {
71
+ try {
72
+ if (!message.artifactId) {
73
+ return sendError(server, id, ws, "NO_ARTIFACT_ID", "artifactId required");
74
+ }
75
+ const store = await loadStore();
76
+ const entry = store.get(message.artifactId);
77
+ if (!entry) {
78
+ return sendError(
79
+ server,
80
+ id,
81
+ ws,
82
+ "ARTIFACT_NOT_FOUND",
83
+ `Artifact not found: ${message.artifactId}`,
84
+ );
85
+ }
86
+ server._send(ws, {
87
+ id,
88
+ type: "artifact-show",
89
+ artifact: { ...entry, storedPath: store.storedPath(entry) },
90
+ });
91
+ } catch (err) {
92
+ sendError(server, id, ws, "ARTIFACT_SHOW_FAILED", err.message);
93
+ }
94
+ }
95
+
96
+ export async function handleArtifactContent(server, id, ws, message) {
97
+ try {
98
+ if (!message.artifactId) {
99
+ return sendError(server, id, ws, "NO_ARTIFACT_ID", "artifactId required");
100
+ }
101
+ const store = await loadStore();
102
+ const entry = store.get(message.artifactId);
103
+ if (!entry) {
104
+ return sendError(
105
+ server,
106
+ id,
107
+ ws,
108
+ "ARTIFACT_NOT_FOUND",
109
+ `Artifact not found: ${message.artifactId}`,
110
+ );
111
+ }
112
+ // Traversal guard: the stored filename must be a plain basename. A
113
+ // tampered index row ("file":"../../secret") must not read outside the
114
+ // files dir.
115
+ if (
116
+ !entry.file ||
117
+ entry.file !== path.basename(entry.file) ||
118
+ entry.file.includes("..")
119
+ ) {
120
+ return sendError(
121
+ server,
122
+ id,
123
+ ws,
124
+ "ARTIFACT_BAD_FILE",
125
+ "artifact index row has a non-basename stored filename — refusing to serve it",
126
+ );
127
+ }
128
+ const previewKind = previewKindForMime(entry.mime);
129
+ const base = {
130
+ id,
131
+ type: "artifact-content",
132
+ artifactId: entry.id,
133
+ mime: entry.mime,
134
+ size: entry.size,
135
+ };
136
+ if (previewKind === "none") {
137
+ return server._send(ws, {
138
+ ...base,
139
+ previewable: false,
140
+ reason: `no inline preview for ${entry.mime} — use \`cc artifacts open ${entry.id}\` locally`,
141
+ });
142
+ }
143
+ const storedPath = store.storedPath(entry);
144
+ let actualSize;
145
+ try {
146
+ actualSize = statSync(storedPath).size;
147
+ } catch {
148
+ return sendError(
149
+ server,
150
+ id,
151
+ ws,
152
+ "ARTIFACT_FILE_MISSING",
153
+ `stored copy is missing: ${entry.file}`,
154
+ );
155
+ }
156
+ if (previewKind === "image") {
157
+ if (actualSize > IMAGE_PREVIEW_CAP) {
158
+ return server._send(ws, {
159
+ ...base,
160
+ previewable: false,
161
+ reason: `image exceeds the ${IMAGE_PREVIEW_CAP} byte preview cap`,
162
+ });
163
+ }
164
+ const body = readFileSync(storedPath);
165
+ return server._send(ws, {
166
+ ...base,
167
+ previewable: true,
168
+ encoding: "base64",
169
+ truncated: false,
170
+ content: body.toString("base64"),
171
+ });
172
+ }
173
+ // text
174
+ const cap = Math.min(
175
+ Number(message.maxBytes) > 0 ? Number(message.maxBytes) : TEXT_PREVIEW_CAP,
176
+ TEXT_PREVIEW_CAP,
177
+ );
178
+ const text = readFileSync(storedPath, "utf-8");
179
+ const truncated = text.length > cap;
180
+ server._send(ws, {
181
+ ...base,
182
+ previewable: true,
183
+ encoding: "utf8",
184
+ truncated,
185
+ content: truncated ? text.slice(0, cap) : text,
186
+ });
187
+ } catch (err) {
188
+ sendError(server, id, ws, "ARTIFACT_CONTENT_FAILED", err.message);
189
+ }
190
+ }
191
+
192
+ export async function handleArtifactRemove(server, id, ws, message) {
193
+ try {
194
+ if (!message.artifactId) {
195
+ return sendError(server, id, ws, "NO_ARTIFACT_ID", "artifactId required");
196
+ }
197
+ const store = await loadStore();
198
+ const found = store.remove(message.artifactId);
199
+ server._send(ws, {
200
+ id,
201
+ type: "artifact-remove",
202
+ artifactId: message.artifactId,
203
+ removed: found ? message.artifactId : null,
204
+ found,
205
+ });
206
+ } catch (err) {
207
+ sendError(server, id, ws, "ARTIFACT_REMOVE_FAILED", err.message);
208
+ }
209
+ }
210
+
211
+ export async function handleArtifactClean(server, id, ws, message) {
212
+ try {
213
+ const store = await loadStore();
214
+ const { removed } = store.cleanupExpired();
215
+ server._send(ws, { id, type: "artifact-clean", removed });
216
+ } catch (err) {
217
+ sendError(server, id, ws, "ARTIFACT_CLEAN_FAILED", err.message);
218
+ }
219
+ }
@@ -185,6 +185,12 @@ export function createWsMessageDispatcher(server) {
185
185
  "bg-stop": () => server._handleBgStop(clientId, id, ws, message),
186
186
  "bg-rename": () => server._handleBgRename(id, ws, message),
187
187
  "bg-resume": () => server._handleBgResume(id, ws, message),
188
+ "artifact-list": () => server._handleArtifactList(id, ws, message),
189
+ "artifact-show": () => server._handleArtifactShow(id, ws, message),
190
+ "artifact-content": () =>
191
+ server._handleArtifactContent(id, ws, message),
192
+ "artifact-remove": () => server._handleArtifactRemove(id, ws, message),
193
+ "artifact-clean": () => server._handleArtifactClean(id, ws, message),
188
194
  "chat.intent.understand": () =>
189
195
  server._handleChatIntentUnderstand(id, ws, message),
190
196
  "chat.intent.understand-stream": () =>
@@ -106,6 +106,13 @@ import {
106
106
  handleBgStopTurn,
107
107
  handleBgView,
108
108
  } from "./background-agent-protocol.js";
109
+ import {
110
+ handleArtifactClean,
111
+ handleArtifactContent,
112
+ handleArtifactList,
113
+ handleArtifactRemove,
114
+ handleArtifactShow,
115
+ } from "./artifact-protocol.js";
109
116
 
110
117
  const __filename = fileURLToPath(import.meta.url);
111
118
  const __dirname = dirname(__filename);
@@ -717,6 +724,31 @@ export class ChainlessChainWSServer extends EventEmitter {
717
724
  return handleBgResume(this, id, ws, message);
718
725
  }
719
726
 
727
+ /** @private — list agent-published artifacts (metadata only) */
728
+ async _handleArtifactList(id, ws, message) {
729
+ return handleArtifactList(this, id, ws, message);
730
+ }
731
+
732
+ /** @private — one artifact's metadata + stored path */
733
+ async _handleArtifactShow(id, ws, message) {
734
+ return handleArtifactShow(this, id, ws, message);
735
+ }
736
+
737
+ /** @private — inline preview content (text utf8 / image base64, capped) */
738
+ async _handleArtifactContent(id, ws, message) {
739
+ return handleArtifactContent(this, id, ws, message);
740
+ }
741
+
742
+ /** @private — remove one artifact (stored copy + index row) */
743
+ async _handleArtifactRemove(id, ws, message) {
744
+ return handleArtifactRemove(this, id, ws, message);
745
+ }
746
+
747
+ /** @private — drop expired artifacts (TTL) */
748
+ async _handleArtifactClean(id, ws, message) {
749
+ return handleArtifactClean(this, id, ws, message);
750
+ }
751
+
720
752
  /** @private */
721
753
  async _handleCompressionStats(id, ws, message) {
722
754
  return handleCompressionStats(this, id, ws, message);
package/src/index.js CHANGED
@@ -156,6 +156,15 @@ import { registerServeCommand } from "./commands/serve.js";
156
156
  // Remote Control unified entry (第四阶段 #1)
157
157
  import { registerRemoteControlCommand } from "./commands/remote-control.js";
158
158
 
159
+ // Agenda: consumer for agent-scheduled wakeups/crons/monitors (第四阶段 #3)
160
+ import { registerAgendaCommand } from "./commands/agenda.js";
161
+
162
+ // Batch: dynamic worktree batch — split a change into parallel units (第四阶段 #4)
163
+ import { registerBatchCommand } from "./commands/batch.js";
164
+
165
+ // Artifacts: agent-published deliverable store (P1 #10)
166
+ import { registerArtifactsCommand } from "./commands/artifacts.js";
167
+
159
168
  // Web UI
160
169
  import { registerUiCommand } from "./commands/ui.js";
161
170
  import { registerPackCommand } from "./commands/pack.js";
@@ -586,6 +595,15 @@ export function createProgram(opts = {}) {
586
595
  // Remote Control unified entry
587
596
  registerRemoteControlCommand(program);
588
597
 
598
+ // Agenda: fire agent-scheduled wakeups/crons/monitors
599
+ registerAgendaCommand(program);
600
+
601
+ // Batch: dynamic worktree batch
602
+ registerBatchCommand(program);
603
+
604
+ // Artifacts: agent-published deliverable store
605
+ registerArtifactsCommand(program);
606
+
589
607
  // Web UI
590
608
  registerUiCommand(program);
591
609
 
@@ -0,0 +1,55 @@
1
+ /**
2
+ * `cc agent --ax-screen-reader` — screen-reader friendly output.
3
+ *
4
+ * Screen readers linearize the terminal: colors are invisible, and anything
5
+ * that repaints in place (the context status line) reads as noisy duplicate
6
+ * lines. This mode therefore:
7
+ * - forces the `mono` theme (chalk level 0 — no ANSI color codes) in the
8
+ * interactive REPL (consumed via CC_SCREEN_READER in agent-repl.js);
9
+ * - disables the repainting status line (reuses the existing
10
+ * CC_STATUSLINE=0 kill-switch).
11
+ * Headless (-p) text output is already plain sequential lines.
12
+ *
13
+ * Ambient activation: CC_SCREEN_READER=1 (so wrappers/launchers can turn it
14
+ * on without threading a flag through every entry point).
15
+ */
16
+
17
+ /** env switches applied by --ax-screen-reader. */
18
+ export const SCREEN_READER_SWITCHES = Object.freeze({
19
+ CC_SCREEN_READER: "1", // REPL: force mono theme / no ANSI decorations
20
+ CC_STATUSLINE: "0", // in-place repainting status line off
21
+ });
22
+
23
+ /**
24
+ * Apply screen-reader mode to an env (default process.env).
25
+ * @returns {string[]} the switch names applied (for the startup notice)
26
+ */
27
+ export function applyScreenReaderMode(env = process.env) {
28
+ for (const [k, v] of Object.entries(SCREEN_READER_SWITCHES)) {
29
+ env[k] = v;
30
+ }
31
+ return Object.keys(SCREEN_READER_SWITCHES);
32
+ }
33
+
34
+ /**
35
+ * Whether screen-reader mode is requested — the explicit `--ax-screen-reader`
36
+ * flag or the ambient CC_SCREEN_READER env var (1 / true / yes / on).
37
+ * @param {{ axScreenReader?: boolean }} [opts]
38
+ * @param {object} [env=process.env]
39
+ * @returns {boolean}
40
+ */
41
+ export function screenReaderRequested(opts = {}, env = process.env) {
42
+ if (opts && opts.axScreenReader === true) return true;
43
+ return screenReaderActive(env);
44
+ }
45
+
46
+ /**
47
+ * Whether screen-reader mode is ACTIVE in the environment (what downstream
48
+ * consumers — the REPL theme init — should check).
49
+ * @param {object} [env=process.env]
50
+ * @returns {boolean}
51
+ */
52
+ export function screenReaderActive(env = process.env) {
53
+ const raw = env.CC_SCREEN_READER;
54
+ return raw != null && /^(1|true|yes|on)$/i.test(String(raw).trim());
55
+ }
@@ -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
+ }