chainlesschain 0.162.156 → 0.162.158

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 (195) 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-DjBXlypQ.js → AIOps-BwpTJTN_.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-Cv9er9bp.js → ActionButton-BtQOoo3l.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-CLKDjSNs.js → Analytics-Duf_rXZa.js} +3 -3
  7. package/src/assets/web-panel/assets/AppLayout-XQK3wjvB.js +9 -0
  8. package/src/assets/web-panel/assets/Artifacts-CTVt96zW.css +1 -0
  9. package/src/assets/web-panel/assets/Artifacts-DQvJoQ6E.js +1 -0
  10. package/src/assets/web-panel/assets/{Audit-CXafD178.js → Audit-DNjOGQLh.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-CP5c-GSd.js → BackgroundAgents-FH9RAfB5.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-BbYLQfyt.js → Backup-ZIbrR4-U.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-Bqe0MwZL.js → BaseInput-DSt3NaND.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-jNA_yUPH.js → Chat-CY-wvbjv.js} +6 -6
  15. package/src/assets/web-panel/assets/Chat-KSGPtU0L.css +1 -0
  16. package/src/assets/web-panel/assets/{ChatBubbleRenderer-BOFLF01s.js → ChatBubbleRenderer-BAV9V-t-.js} +1 -1
  17. package/src/assets/web-panel/assets/{Checkbox-Dlzz-zD1.js → Checkbox-8JVadE_B.js} +1 -1
  18. package/src/assets/web-panel/assets/{Codegen-BAsDQjGf.js → Codegen-CN83nPYI.js} +1 -1
  19. package/src/assets/web-panel/assets/{Col-Ju0HrAuS.js → Col-xPSvqdN8.js} +1 -1
  20. package/src/assets/web-panel/assets/{Community-DynJd43Z.js → Community-D78cnIjG.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compact-CabbZVww.js → Compact-CSw6Csbp.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compliance-DW4BmAoh.js → Compliance-E_HhjcYR.js} +1 -1
  23. package/src/assets/web-panel/assets/{Cowork-Bk1S_7X-.js → Cowork-DybeQjmm.js} +3 -3
  24. package/src/assets/web-panel/assets/{Cron-By4sh_NM.js → Cron-DS0BJeLY.js} +2 -2
  25. package/src/assets/web-panel/assets/{Crosschain-DdZdbS1C.js → Crosschain-BOwVgkbs.js} +1 -1
  26. package/src/assets/web-panel/assets/{DID-CkL-sQyS.js → DID-DRUr9YuX.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dashboard-g_mOpumg.js → Dashboard-Cht-yUKW.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dropdown-DtHyE7Ao.js → Dropdown-BLr_EJ9X.js} +1 -1
  29. package/src/assets/web-panel/assets/{EmailListRenderer-Kpvov5Wr.js → EmailListRenderer-Drexuh5W.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-C4kZoePR.js → FamilyGuardDashboard-D9Q2Kq63.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-BAwWoK2N.js → Federation-C-Z1xI-T.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-DeI0i5f8.js → FormItemContext-DLHPMkQF.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-CkcKRtos.js → GenericCardRenderer-DxxEdn2r.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-kX6jjI9y.js → Git-syATcaBt.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-BXJCBm6x.js → Governance-CYe_r8Zf.js} +1 -1
  36. package/src/assets/web-panel/assets/Inference-GWxErB7k.js +1 -0
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-3Dnuf1Wh.js → KnowledgeGraph-qQvVCZoi.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-lVAZCtUB.js → Logs-BenAU2W8.js} +2 -2
  39. package/src/assets/web-panel/assets/MarkdownRenderer-7agE3Or1.js +1 -0
  40. package/src/assets/web-panel/assets/MarkdownRenderer-DbzhFwvz.css +1 -0
  41. package/src/assets/web-panel/assets/Marketplace-D4Jn5fpD.js +1 -0
  42. package/src/assets/web-panel/assets/{McpTools-CqjzefRU.js → McpTools-DtAwoqiQ.js} +4 -4
  43. package/src/assets/web-panel/assets/{Memory-BFiPAxk6.js → Memory-BI_O50I8.js} +2 -2
  44. package/src/assets/web-panel/assets/{MobileBridge-D0O43VwF.js → MobileBridge-CQWE2lqx.js} +1 -1
  45. package/src/assets/web-panel/assets/{MobileProjects-B3now48t.js → MobileProjects-CVtTqgtg.js} +1 -1
  46. package/src/assets/web-panel/assets/{Mtc-DybhtaCS.js → Mtc-Dnr16qsn.js} +2 -2
  47. package/src/assets/web-panel/assets/{MtcAudit-D7MmY4F0.js → MtcAudit-CQ5Xe4-a.js} +5 -5
  48. package/src/assets/web-panel/assets/{Multisig-wZvGJnB8.js → Multisig-Bjr8O-wy.js} +3 -3
  49. package/src/assets/web-panel/assets/{NLProgramming-BNPpgfvn.js → NLProgramming-CEe-KirU.js} +1 -1
  50. package/src/assets/web-panel/assets/{Notes-B6FS3KBj.js → Notes-spKJIKF9.js} +4 -4
  51. package/src/assets/web-panel/assets/{NotificationSettings-CK0yla9U.js → NotificationSettings-DRp83LFG.js} +1 -1
  52. package/src/assets/web-panel/assets/OrderTableRenderer-h1A0XB2a.js +1 -0
  53. package/src/assets/web-panel/assets/{Organization-geqgwN1T.js → Organization-BlgB0IYg.js} +4 -4
  54. package/src/assets/web-panel/assets/{Overflow-BWJYZFbC.js → Overflow-a_35HqZI.js} +1 -1
  55. package/src/assets/web-panel/assets/{P2P-DnU1505F.js → P2P-CSNHCNPC.js} +2 -2
  56. package/src/assets/web-panel/assets/{PdhVaultBrowser-BGY74KnC.js → PdhVaultBrowser-C8M1JH-1.js} +5 -5
  57. package/src/assets/web-panel/assets/{Permissions-CgArrWtQ.js → Permissions-DmjJi9Ms.js} +4 -4
  58. package/src/assets/web-panel/assets/{PersonalDataHub-C2vYE61a.js → PersonalDataHub-DfuwRyf4.js} +2 -2
  59. package/src/assets/web-panel/assets/{Pipeline-CBYLI4yW.js → Pipeline-B2-S4ekE.js} +1 -1
  60. package/src/assets/web-panel/assets/{Privacy-B-PdW9aN.js → Privacy-B1dOiBLC.js} +1 -1
  61. package/src/assets/web-panel/assets/{ProjectInit-CuWQegbV.js → ProjectInit-_pNmc-DX.js} +2 -2
  62. package/src/assets/web-panel/assets/{ProjectSettings-4d32m_cS.js → ProjectSettings-DmwG2Ii1.js} +2 -2
  63. package/src/assets/web-panel/assets/{Projects-VLA7ttwd.js → Projects-COATmHxF.js} +1 -1
  64. package/src/assets/web-panel/assets/{Providers-B_Izb_oa.js → Providers-C6HSjg1d.js} +1 -1
  65. package/src/assets/web-panel/assets/{QrScannerModal-C6B_ndXV.js → QrScannerModal-CrDmS7l1.js} +1 -1
  66. package/src/assets/web-panel/assets/{QuickAsk-C6tavL-y.js → QuickAsk-CfKvWt-x.js} +1 -1
  67. package/src/assets/web-panel/assets/{Recommend-CF-LaZ6x.js → Recommend-DMa6Kuzv.js} +1 -1
  68. package/src/assets/web-panel/assets/RemoteSession-DrktUo5x.js +4 -0
  69. package/src/assets/web-panel/assets/Reputation-BTPQ1vjL.js +1 -0
  70. package/src/assets/web-panel/assets/{Row-DO0fd48A.js → Row-B6Q7QkMG.js} +1 -1
  71. package/src/assets/web-panel/assets/{RssFeed-BV5LDzFR.js → RssFeed-B1IJhAmX.js} +3 -3
  72. package/src/assets/web-panel/assets/Search-xbea1kGD.js +1 -0
  73. package/src/assets/web-panel/assets/{Security-DwSrWRCw.js → Security-7rs0cO3F.js} +4 -4
  74. package/src/assets/web-panel/assets/{Services-BEf1QyM4.js → Services-0fjixK0S.js} +2 -2
  75. package/src/assets/web-panel/assets/{Skeleton-LZXTpovy.js → Skeleton-B_rVq9Cu.js} +1 -1
  76. package/src/assets/web-panel/assets/{Skills-SKgc0Zug.js → Skills-C5uM90b9.js} +1 -1
  77. package/src/assets/web-panel/assets/{Sla-C_QVOtkN.js → Sla-U4pYCIZH.js} +1 -1
  78. package/src/assets/web-panel/assets/{SpeechSettings-v5TW2V-X.js → SpeechSettings-Cdz4yTWx.js} +1 -1
  79. package/src/assets/web-panel/assets/{SyncSettings-CQPjHdqU.js → SyncSettings-Me3aetHv.js} +2 -2
  80. package/src/assets/web-panel/assets/{Tasks-DCd-uYcI.js → Tasks-Cf6aNkL_.js} +1 -1
  81. package/src/assets/web-panel/assets/{Templates-62VDIjtj.js → Templates-CYatk4tU.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tenant-BnFQ0pNy.js → Tenant-DIMKLw7o.js} +1 -1
  83. package/src/assets/web-panel/assets/{Terminal-D3v0MGSj.js → Terminal-C_cp-uSJ.js} +2 -2
  84. package/src/assets/web-panel/assets/{TimelineRenderer-BI1Ka-cu.js → TimelineRenderer-cUPVNADp.js} +1 -1
  85. package/src/assets/web-panel/assets/{Tokens-BwX1jXYP.js → Tokens-CiaEIXVf.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trigger-CSA8TqNx.js → Trigger-DB1WU5lQ.js} +1 -1
  87. package/src/assets/web-panel/assets/{Trust-Bn3IjCx7.js → Trust-Cfht9Ijy.js} +1 -1
  88. package/src/assets/web-panel/assets/{UkeySign-pUbOAqNi.js → UkeySign-K3P4Vt9S.js} +1 -1
  89. package/src/assets/web-panel/assets/{VideoEditing-Db2RercF.js → VideoEditing-DWjhjXRh.js} +1 -1
  90. package/src/assets/web-panel/assets/{Wallet-CpR1BVAl.js → Wallet-D9-Q1Kan.js} +4 -4
  91. package/src/assets/web-panel/assets/{WebAuthn-Yxtkn7Zp.js → WebAuthn-BDszy-oV.js} +5 -5
  92. package/src/assets/web-panel/assets/{WorkflowEditor-CYLe9a2E.js → WorkflowEditor-DIhij4rA.js} +1 -1
  93. package/src/assets/web-panel/assets/{chat-gsOGdak0.js → chat-D7VCiKbh.js} +1 -1
  94. package/src/assets/web-panel/assets/{colors-D9fN299a.js → colors-BvOd9pVn.js} +1 -1
  95. package/src/assets/web-panel/assets/{compact-item-vkd7s-ay.js → compact-item-DoqXBiAO.js} +1 -1
  96. package/src/assets/web-panel/assets/{createContext-C7YkbMyy.js → createContext-CrKRzUtp.js} +1 -1
  97. package/src/assets/web-panel/assets/devWarning-nDB--2xr.js +1 -0
  98. package/src/assets/web-panel/assets/{hasIn-ig7reRSU.js → hasIn-BWAev4WC.js} +1 -1
  99. package/src/assets/web-panel/assets/{icons-DEmcnxBf.js → icons-DG5UD-6K.js} +4 -4
  100. package/src/assets/web-panel/assets/{index-W52sHRdr.js → index-1poMpMU0.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DZAxCnjS.js → index-8g5TJCZ3.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-9TxdMYju.js → index-B0vGEbJT.js} +1 -1
  103. package/src/assets/web-panel/assets/index-B1_pRTtB.js +1 -0
  104. package/src/assets/web-panel/assets/{index-DhdXbsXX.js → index-B30NvR21.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-B7QRpKif.js → index-BCyP071m.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Bt-9ZhIu.js → index-BHOcVYrx.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BYe9HRSt.js → index-BN9m4Ux2.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BkP17Z95.js → index-BWWsaimP.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CDWTycrg.js → index-BXyl8O23.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CspGymmg.js → index-BZGk8AbZ.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DFfxF7Vt.js → index-Bf9YlbDn.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DQ8U7kzC.js → index-Bj7EgmOp.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-D2yhtk6a.js → index-BkgnGX7v.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-FzGr6CVk.js → index-BoYrS_L2.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BMw48d19.js → index-Bq_ctRpz.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CAkz1f1r.js → index-C5kRmj3P.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-SDrcQXO1.js → index-CDGLdn12.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-gj8kxcAi.js → index-CEPHMSih.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-PkH39qsp.js → index-CEXfKSiI.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BHHN_KTS.js → index-CSb-tnIo.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-gpurplyq.js → index-CmJ6xKBO.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BfpVcXKa.js → index-CoD31Ndm.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CGzGe5tv.js → index-Cx7M7PSF.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-BhftELHI.js → index-D23L1V76.js} +3 -3
  125. package/src/assets/web-panel/assets/{index-DPguq3X7.js → index-DV-Rfmfx.js} +4 -4
  126. package/src/assets/web-panel/assets/{index-BpGlLbRv.js → index-DlGu7As6.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-C8HZMygt.js → index-DvDOpsJB.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DuwbKHoS.js → index-HwHdTusY.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-C7AY39sg.js → index-ISvPZ05r.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-nX8YE6cj.js → index-JzUmJMvn.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DvCJXmrx.js → index-KR1-Em9C.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-Co2NYyew.js → index-PDxGjKRu.js} +1 -1
  133. package/src/assets/web-panel/assets/index-TMM9FuW-.js +1 -0
  134. package/src/assets/web-panel/assets/{index-D6s_kbjt.js → index-YGDy7nNq.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-Dy_TKZ9h.js → index-gmxqu5Kk.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-DFM1oNe1.js → index-oY45XrkG.js} +1 -1
  137. package/src/assets/web-panel/assets/{index-bVbgYBhv.js → index-ww1QtLnX.js} +1 -1
  138. package/src/assets/web-panel/assets/{index-BvDEukXe.js → index-zH-tuuGZ.js} +1 -1
  139. package/src/assets/web-panel/assets/{initDefaultProps-BrgfVRg9.js → initDefaultProps-XW-OR3mL.js} +1 -1
  140. package/src/assets/web-panel/assets/{motion-D_3xmwac.js → motion-CeuEHh20.js} +1 -1
  141. package/src/assets/web-panel/assets/{move-DDDJdlxw.js → move-Dva79Keg.js} +1 -1
  142. package/src/assets/web-panel/assets/{mtc-parser-BK2uEWwn.js → mtc-parser-C0B6bD2j.js} +1 -1
  143. package/src/assets/web-panel/assets/{omit-BPHKbDcf.js → omit-AkZvO-a2.js} +1 -1
  144. package/src/assets/web-panel/assets/{pickAttrs-LNnTscuz.js → pickAttrs-B4r55qGc.js} +1 -1
  145. package/src/assets/web-panel/assets/{placementArrow-CvmTZll5.js → placementArrow-BLH-z7YE.js} +1 -1
  146. package/src/assets/web-panel/assets/{responsiveObserve-eBj7qi2J.js → responsiveObserve-ksEGZ2Wf.js} +1 -1
  147. package/src/assets/web-panel/assets/{slide-D68hHkrM.js → slide-Cth4OD1Z.js} +1 -1
  148. package/src/assets/web-panel/assets/{statusUtils-daTlaeo0.js → statusUtils-9S83ezxw.js} +1 -1
  149. package/src/assets/web-panel/assets/{styleChecker-DtyKMtHB.js → styleChecker-7yQmQGZ4.js} +1 -1
  150. package/src/assets/web-panel/assets/{useFlexGapSupport-ekFwFs9h.js → useFlexGapSupport-DpGnMmPc.js} +1 -1
  151. package/src/assets/web-panel/assets/{useFs-jRaSEoF4.js → useFs-C4zDZIXH.js} +1 -1
  152. package/src/assets/web-panel/assets/{usePersonalDataHub-DD5nGzjA.js → usePersonalDataHub-CoLICdUZ.js} +1 -1
  153. package/src/assets/web-panel/assets/{vnode-KcgLUiom.js → vnode-CKyH-35X.js} +1 -1
  154. package/src/assets/web-panel/assets/{zoom-Cyk2PQ-s.js → zoom-DRDGit17.js} +1 -1
  155. package/src/assets/web-panel/index.html +2 -2
  156. package/src/command-manifest.json +8 -1
  157. package/src/commands/agent.js +49 -5
  158. package/src/commands/artifacts.js +161 -0
  159. package/src/commands/browse.js +204 -2
  160. package/src/data/changelog.json +13 -2
  161. package/src/gateways/ws/artifact-protocol.js +219 -0
  162. package/src/gateways/ws/message-dispatcher.js +6 -0
  163. package/src/gateways/ws/ws-server.js +32 -0
  164. package/src/index.js +6 -0
  165. package/src/lib/accessibility.js +55 -0
  166. package/src/lib/artifact-store.js +236 -0
  167. package/src/lib/chrome-connector.js +349 -0
  168. package/src/lib/hook-runner.cjs +47 -7
  169. package/src/lib/ide-context.js +84 -0
  170. package/src/lib/permission-rules.cjs +26 -3
  171. package/src/lib/plugin-runtime/scopes.js +12 -0
  172. package/src/lib/remote-control.js +35 -0
  173. package/src/lib/safe-mode.js +40 -0
  174. package/src/lib/shell-selector.cjs +203 -0
  175. package/src/lib/skill-loader.js +19 -0
  176. package/src/lib/slash-dispatch.js +34 -0
  177. package/src/lib/web-ui-server.js +120 -7
  178. package/src/repl/agent-repl.js +330 -116
  179. package/src/repl/remote-approval.js +111 -0
  180. package/src/runtime/agent-core.js +129 -8
  181. package/src/runtime/coding-agent-contract-shared.cjs +105 -0
  182. package/src/runtime/coding-agent-policy.cjs +31 -0
  183. package/src/runtime/headless-stream.js +51 -1
  184. package/src/runtime/policies/agent-policy.js +4 -0
  185. package/src/assets/web-panel/assets/AppLayout-CfyZNzmJ.js +0 -9
  186. package/src/assets/web-panel/assets/Chat-DDh58PCf.css +0 -1
  187. package/src/assets/web-panel/assets/Inference-ujD2JSKo.js +0 -1
  188. package/src/assets/web-panel/assets/Marketplace-qnzNPm-A.js +0 -1
  189. package/src/assets/web-panel/assets/OrderTableRenderer-CXfPHIpy.js +0 -1
  190. package/src/assets/web-panel/assets/RemoteSession-DBqTTQ9_.js +0 -4
  191. package/src/assets/web-panel/assets/Reputation-B01FALUi.js +0 -1
  192. package/src/assets/web-panel/assets/Search-CTti4_MP.js +0 -1
  193. package/src/assets/web-panel/assets/devWarning-DEcR2h6k.js +0 -1
  194. package/src/assets/web-panel/assets/index-BEibanjg.js +0 -1
  195. package/src/assets/web-panel/assets/index-z_25tU3p.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
@@ -162,6 +162,9 @@ import { registerAgendaCommand } from "./commands/agenda.js";
162
162
  // Batch: dynamic worktree batch — split a change into parallel units (第四阶段 #4)
163
163
  import { registerBatchCommand } from "./commands/batch.js";
164
164
 
165
+ // Artifacts: agent-published deliverable store (P1 #10)
166
+ import { registerArtifactsCommand } from "./commands/artifacts.js";
167
+
165
168
  // Web UI
166
169
  import { registerUiCommand } from "./commands/ui.js";
167
170
  import { registerPackCommand } from "./commands/pack.js";
@@ -598,6 +601,9 @@ export function createProgram(opts = {}) {
598
601
  // Batch: dynamic worktree batch
599
602
  registerBatchCommand(program);
600
603
 
604
+ // Artifacts: agent-published deliverable store
605
+ registerArtifactsCommand(program);
606
+
601
607
  // Web UI
602
608
  registerUiCommand(program);
603
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,236 @@
1
+ /**
2
+ * ArtifactStore — durable home for agent DELIVERABLES (gap-analysis P1 #10).
3
+ *
4
+ * An agent run produces important files — a Markdown report, a patch, a
5
+ * screenshot, test logs, review findings JSON — that otherwise live as loose
6
+ * paths the user has to remember. `publish_artifact` copies such a file into
7
+ * `~/.chainlesschain/artifacts/` with metadata (title, kind, mime, sha256,
8
+ * session), so:
9
+ * - the session transcript only records the small METADATA object (the tool
10
+ * result), never the file body;
11
+ * - `cc artifacts` lists/inspects/cleans them across sessions;
12
+ * - web-panel / remote surfaces can later serve previews from one place.
13
+ *
14
+ * Layout:
15
+ * ~/.chainlesschain/artifacts/index.jsonl one metadata row per artifact
16
+ * ~/.chainlesschain/artifacts/files/<id><ext> the stored copy
17
+ *
18
+ * Per-row tolerant JSONL (a corrupt line never poisons the store); injected
19
+ * clock + dir for unit tests; TTL-based expiry with an explicit cleanup call
20
+ * (`cc artifacts clean` / opportunistic on publish).
21
+ */
22
+
23
+ import fs from "fs";
24
+ import os from "os";
25
+ import path from "path";
26
+ import crypto from "crypto";
27
+
28
+ export const ARTIFACT_KINDS = Object.freeze([
29
+ "report",
30
+ "patch",
31
+ "screenshot",
32
+ "log",
33
+ "data",
34
+ "other",
35
+ ]);
36
+
37
+ /** Copy-size guard: an "artifact" is a deliverable, not a build tree. */
38
+ export const MAX_ARTIFACT_BYTES = 100 * 1024 * 1024; // 100 MB
39
+
40
+ /** Default retention before `clean` removes an artifact. */
41
+ export const DEFAULT_TTL_DAYS = 30;
42
+
43
+ const MIME_BY_EXT = Object.freeze({
44
+ ".md": "text/markdown",
45
+ ".markdown": "text/markdown",
46
+ ".txt": "text/plain",
47
+ ".log": "text/plain",
48
+ ".json": "application/json",
49
+ ".html": "text/html",
50
+ ".htm": "text/html",
51
+ ".csv": "text/csv",
52
+ ".xml": "application/xml",
53
+ ".yaml": "application/yaml",
54
+ ".yml": "application/yaml",
55
+ ".patch": "text/x-patch",
56
+ ".diff": "text/x-patch",
57
+ ".png": "image/png",
58
+ ".jpg": "image/jpeg",
59
+ ".jpeg": "image/jpeg",
60
+ ".gif": "image/gif",
61
+ ".webp": "image/webp",
62
+ ".svg": "image/svg+xml",
63
+ ".pdf": "application/pdf",
64
+ ".zip": "application/zip",
65
+ });
66
+
67
+ export function artifactsDir(homedir = os.homedir()) {
68
+ // Test/deployment override (same convention as CC_BACKGROUND_AGENTS_DIR).
69
+ if (process.env.CC_ARTIFACTS_DIR) return process.env.CC_ARTIFACTS_DIR;
70
+ return path.join(homedir, ".chainlesschain", "artifacts");
71
+ }
72
+
73
+ function mimeForExt(ext) {
74
+ return (
75
+ MIME_BY_EXT[String(ext || "").toLowerCase()] || "application/octet-stream"
76
+ );
77
+ }
78
+
79
+ function normalizeKind(kind) {
80
+ const k = typeof kind === "string" ? kind.trim().toLowerCase() : "";
81
+ return ARTIFACT_KINDS.includes(k) ? k : "other";
82
+ }
83
+
84
+ export class ArtifactStore {
85
+ constructor({ dir = null, now = () => Date.now() } = {}) {
86
+ this.dir = dir || artifactsDir();
87
+ this._now = typeof now === "function" ? now : () => now;
88
+ }
89
+
90
+ _indexFile() {
91
+ return path.join(this.dir, "index.jsonl");
92
+ }
93
+
94
+ _filesDir() {
95
+ return path.join(this.dir, "files");
96
+ }
97
+
98
+ _ensureDirs() {
99
+ fs.mkdirSync(this._filesDir(), { recursive: true });
100
+ }
101
+
102
+ /**
103
+ * Publish a file as an artifact (copy + metadata row).
104
+ * @param {{ filePath: string, title?: string, kind?: string,
105
+ * sessionId?: string|null, ttlDays?: number }} opts
106
+ * @returns {object} the metadata entry (what the tool returns — no body)
107
+ */
108
+ publish({ filePath, title, kind, sessionId = null, ttlDays } = {}) {
109
+ if (!filePath) throw new Error("publish requires a filePath");
110
+ const abs = path.resolve(filePath);
111
+ let stat;
112
+ try {
113
+ stat = fs.statSync(abs);
114
+ } catch {
115
+ throw new Error(`artifact source not found: ${abs}`);
116
+ }
117
+ if (!stat.isFile()) {
118
+ throw new Error(`artifact source is not a regular file: ${abs}`);
119
+ }
120
+ if (stat.size > MAX_ARTIFACT_BYTES) {
121
+ throw new Error(
122
+ `artifact too large (${stat.size} bytes > ${MAX_ARTIFACT_BYTES}); artifacts are deliverables, not build output`,
123
+ );
124
+ }
125
+ const body = fs.readFileSync(abs);
126
+ const sha256 = crypto.createHash("sha256").update(body).digest("hex");
127
+ const ext = path.extname(abs);
128
+ const id = `art_${this._now().toString(36)}_${crypto
129
+ .randomBytes(4)
130
+ .toString("hex")}`;
131
+ const days = Number.isFinite(Number(ttlDays))
132
+ ? Math.max(1, Number(ttlDays))
133
+ : DEFAULT_TTL_DAYS;
134
+ const createdAt = this._now();
135
+ const entry = {
136
+ id,
137
+ title: String(title || path.basename(abs)),
138
+ kind: normalizeKind(kind),
139
+ mime: mimeForExt(ext),
140
+ size: stat.size,
141
+ sha256,
142
+ sourcePath: abs,
143
+ file: `${id}${ext}`,
144
+ sessionId: sessionId ? String(sessionId) : null,
145
+ createdAt: new Date(createdAt).toISOString(),
146
+ expiresAt: new Date(createdAt + days * 24 * 60 * 60 * 1000).toISOString(),
147
+ };
148
+ this._ensureDirs();
149
+ fs.writeFileSync(path.join(this._filesDir(), entry.file), body);
150
+ fs.appendFileSync(this._indexFile(), JSON.stringify(entry) + "\n", "utf-8");
151
+ return entry;
152
+ }
153
+
154
+ /** All (non-corrupt) metadata rows, oldest first. */
155
+ list({ sessionId } = {}) {
156
+ let raw;
157
+ try {
158
+ raw = fs.readFileSync(this._indexFile(), "utf-8");
159
+ } catch {
160
+ return [];
161
+ }
162
+ const out = [];
163
+ for (const line of raw.split("\n")) {
164
+ const t = line.trim();
165
+ if (!t) continue;
166
+ try {
167
+ const e = JSON.parse(t);
168
+ if (e && typeof e === "object" && e.id) out.push(e);
169
+ } catch {
170
+ /* per-row tolerance — a corrupt line never poisons the store */
171
+ }
172
+ }
173
+ return sessionId ? out.filter((e) => e.sessionId === sessionId) : out;
174
+ }
175
+
176
+ /** One entry by id (or null). */
177
+ get(id) {
178
+ return this.list().find((e) => e.id === id) || null;
179
+ }
180
+
181
+ /** Absolute path of an artifact's stored copy (or null). */
182
+ storedPath(idOrEntry) {
183
+ const entry =
184
+ typeof idOrEntry === "object" && idOrEntry
185
+ ? idOrEntry
186
+ : this.get(idOrEntry);
187
+ if (!entry) return null;
188
+ return path.join(this._filesDir(), entry.file);
189
+ }
190
+
191
+ /** Remove one artifact (file + index row). @returns {boolean} found */
192
+ remove(id) {
193
+ const entries = this.list();
194
+ const target = entries.find((e) => e.id === id);
195
+ if (!target) return false;
196
+ try {
197
+ fs.rmSync(this.storedPath(target), { force: true });
198
+ } catch {
199
+ /* best-effort — the index row is the source of truth */
200
+ }
201
+ this._rewrite(entries.filter((e) => e.id !== id));
202
+ return true;
203
+ }
204
+
205
+ /**
206
+ * Remove expired artifacts (expiresAt in the past).
207
+ * @returns {{ removed: number }}
208
+ */
209
+ cleanupExpired() {
210
+ const entries = this.list();
211
+ const now = this._now();
212
+ const keep = [];
213
+ let removed = 0;
214
+ for (const e of entries) {
215
+ const exp = Date.parse(e.expiresAt || "");
216
+ if (Number.isFinite(exp) && exp <= now) {
217
+ try {
218
+ fs.rmSync(this.storedPath(e), { force: true });
219
+ } catch {
220
+ /* best-effort */
221
+ }
222
+ removed += 1;
223
+ } else {
224
+ keep.push(e);
225
+ }
226
+ }
227
+ if (removed > 0) this._rewrite(keep);
228
+ return { removed };
229
+ }
230
+
231
+ _rewrite(entries) {
232
+ this._ensureDirs();
233
+ const text = entries.map((e) => JSON.stringify(e)).join("\n");
234
+ fs.writeFileSync(this._indexFile(), text ? text + "\n" : "", "utf-8");
235
+ }
236
+ }