chainlesschain 0.162.163 → 0.162.165

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 (220) hide show
  1. package/package.json +3 -1
  2. package/src/assets/web-panel/assets/{AIOps-BQ0kgfA1.js → AIOps-DgGZr3PR.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-Dr1MDANe.js → ActionButton-BAOgBIc_.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-CZ5vsaXW.js → Analytics-Ds4GAvNF.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-BIBga881.js → AppLayout-CoJvCkoF.js} +4 -4
  6. package/src/assets/web-panel/assets/{Artifacts-vpXHRU3u.js → Artifacts-6EgUaE1T.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-B8exuoub.js → Audit-BYie_i74.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-923evRRN.js → BackgroundAgents-D5gxOhjd.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-sr_sswlQ.js → Backup-CIsVK2pT.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-CnW-4XKT.js → BaseInput-ClorP55M.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-FZCsTZIL.js → Chat-CnhTPGGA.js} +6 -6
  12. package/src/assets/web-panel/assets/ChatBubbleRenderer-Bl1VXgwp.js +1 -0
  13. package/src/assets/web-panel/assets/{Checkbox-DFrxB59H.js → Checkbox-BRavl7TP.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-_b6WEel8.js → Codegen-BXg_ErNT.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-CkbnnZ-K.js → Col-CfDPzFPq.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-BxM_82zW.js → Community-Cgo0httP.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-DYinP45R.js → Compact-DU5JcEQM.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-DyXp3sSP.js → Compliance-DCH_TVBl.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-BEr2DhW7.js → Cowork-IGb5O2IH.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-B-Vwo1hN.js → Cron-CPp_Xp9F.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DbLxKddd.js → Crosschain-Cwrqklzw.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BsQ_LFw9.js → DID-D55sBQwM.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-lVlXQHu5.js → Dashboard-CedXhGmP.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-Beo_foO7.js → Dropdown-Dm6_v3V4.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-DszHIXED.js → EmailListRenderer-CWjnuE3G.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DcAwoh81.js → FamilyGuardDashboard-DubTh7y-.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-CuJLa2eb.js → Federation-DlSM4uJv.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-Cxd1lxhc.js → FormItemContext-BYhg0vtG.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-BBvq5Jsw.js → GenericCardRenderer-Bkh7qjfK.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-WqPKetx6.js → Git-DSLWPKvn.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-CWYtY3ke.js → Governance-DIj9O8BB.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-BnX1iiNV.js → Inference-Dhep3e5Y.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-BJuM8GN5.js → KnowledgeGraph-Cj8vzxB_.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-DqLMNntm.js → Logs-Ba4SsPja.js} +2 -2
  35. package/src/assets/web-panel/assets/MarkdownRenderer-DUjyuVB3.js +1 -0
  36. package/src/assets/web-panel/assets/{Marketplace-_vGfmhNl.js → Marketplace-CGs9IIbf.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-BeJDOkDj.js → McpTools-BTPAoX4K.js} +4 -4
  38. package/src/assets/web-panel/assets/{Memory-Da0EEZDO.js → Memory-BJ6ISf2c.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-DSuzcCCy.js → MobileBridge-Ca6n3Dcn.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-D0RAk4e8.js → MobileProjects-lvilHuLt.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-BE9YgdA0.js → Mtc-CMo0acBV.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-fC2BpO5S.js → MtcAudit-Hz5wBvFX.js} +4 -4
  43. package/src/assets/web-panel/assets/{Multisig-D24Xxqw3.js → Multisig-BK2aKWE6.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-Bs3oGvS8.js → NLProgramming-CExpnYMI.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-BFjmAA-3.js → Notes-B7IFbuBo.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-BX_9Bhv0.js → NotificationSettings-VrGchtb0.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-wjkZwJXI.js → OrderTableRenderer-CyrNTxkU.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-DBcdsVEP.js → Organization-I0C17GBP.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-DDyaF9vC.js → Overflow-CXcWvERe.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-BB78L4he.js → P2P-FFSCXrZq.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-DqTiZFum.js → PdhVaultBrowser-1PB-p8Zk.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-INbnWv4j.js → Permissions-C1YHejsb.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-BST-VmTq.js → PersonalDataHub-CEMPpgf8.js} +2 -2
  54. package/src/assets/web-panel/assets/{Pipeline-CE7_tS2k.js → Pipeline-FoIScdLr.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-hK5L8dOB.js → Privacy-DDfbEzEj.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-BvkkUoDn.js → ProjectInit-Dso4kB0O.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-u7_EUtkm.js → ProjectSettings-WvDoydDc.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-5Fmt_8Nw.js → Projects-NJp0kzkd.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-B9eanarR.js → Providers-BuJXnOLI.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-C0dX5N2f.js → QrScannerModal-Bb3lMvGG.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-CKTpG-PZ.js → QuickAsk-Ddxra3fK.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-Px_w_XQM.js → Recommend-BW-dR9qw.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-Di0fnYkM.js → RemoteSession-Hnm7-Qkm.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-vesnWjXt.js → Reputation-DKz6meHE.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-exbY6LkT.js → Row-BuAsoNTS.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-B3O3x70t.js → RssFeed-_do9DMIH.js} +2 -2
  67. package/src/assets/web-panel/assets/{Search-uRA0DhNz.js → Search-CMlcw7ND.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-CViqdH6K.js → Security-DhAkfSpM.js} +3 -3
  69. package/src/assets/web-panel/assets/{Services-CkXS97WN.js → Services-5zKLhy46.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-DxW5WYnE.js → Skeleton-DCMZ3wfv.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-CLxmGFse.js → Skills-DxQgYCC_.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-Cr5fziXq.js → Sla-CJmE4cSN.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-H0JSb3cp.js → SpeechSettings-BgpRTPQp.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-DMYWMae7.js → SyncSettings-DkogtYbB.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-CbYyDdF4.js → Tasks-D5OrwAl9.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-Cpc8JjiK.js → Templates-DAZIu5nN.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-CRh77Jda.js → Tenant-BRJMI-su.js} +1 -1
  78. package/src/assets/web-panel/assets/Terminal-CMXLqQKF.js +3 -0
  79. package/src/assets/web-panel/assets/{TimelineRenderer-D41jzzbG.js → TimelineRenderer-D4z9aSGD.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-C_jWEy6b.js → Tokens-zgYA4UOI.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-Dk1ZUhsy.js → Trigger-DMOgHX4m.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-CLVy4l7m.js → Trust-CEtoNwcT.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-C-FeqFDj.js → UkeySign-DFx2pQfO.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-CqVZzyMm.js → VideoEditing-DLssy-7B.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-DX9BW9pu.js → Wallet-DQHc6gTl.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-7hQDpWw_.js → WebAuthn-DgKcODkn.js} +2 -2
  87. package/src/assets/web-panel/assets/{WorkflowEditor-6XqUAO0j.js → WorkflowEditor-cPEWfMOf.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-5R_vkaJG.js → chat-Coj7rSO1.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-huUPZdC-.js → colors-DiyGfr-6.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-CfJlgisE.js → compact-item-DB63aMXh.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-jO8heofr.js → createContext-CENpsYHg.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-XvRaIiyt.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DY8Vxqx7.js → hasIn-B9NOmk0K.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-B300RPnT.js → index-76GMjcbq.js} +3 -3
  95. package/src/assets/web-panel/assets/{index-CA6sO5VD.js → index-7IMjHwGU.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-D1VLuww0.js → index-B0C6zl0y.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-D0pet5Uy.js → index-B45osqnZ.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BHE8OIls.js → index-BXzMKVLp.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-vXs3B8GJ.js → index-BkoqtJzV.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-d1xkbtNz.js → index-BpmTl8QC.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BfCx4pFT.js → index-BramLq29.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-p85YaaKw.js → index-BvQq6gX2.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BsBtRWbA.js → index-Bvj07Ab1.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-KRHroryI.js → index-BxvNtndl.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BmqGerpC.js → index-C9d8hdgY.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CA90TZxG.js → index-CAXouogg.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Bxax9vQ6.js → index-CKNt2vfs.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BYLBPpt8.js → index-CRhEJ8JO.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DXBcvUnf.js → index-CTuX_MU0.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Cjxbu68P.js → index-CXRknr9W.js} +1 -1
  111. package/src/assets/web-panel/assets/index-CbO1zSXV.js +1 -0
  112. package/src/assets/web-panel/assets/{index-Cbe9G0V4.js → index-ClEy1v1o.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CRElRhyS.js → index-CmNCDN5t.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-v3pTaEQm.js → index-CvTJ-x6b.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-ChvHubX3.js → index-CwkK9Kl2.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CLhBh-qE.js → index-CxVfbuj9.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BQS-uPOK.js → index-DAMKhjtT.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-C2iVSFpy.js → index-DANoIqXP.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-B74OXFxC.js → index-D_rZCxxI.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-dKx57io7.js → index-Dg0v-CXZ.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BRXOP09X.js → index-DqLC0iE4.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BtEXSoPL.js → index-DsPDEHOg.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DoI2sZm7.js → index-KDCuTzdx.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-BAON7TzU.js → index-Kp2AETEw.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Cis_Ip7H.js → index-U_snupDl.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-j3O2EEJe.js → index-VDFbduzG.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BliaT02i.js → index-Y_UKQpsV.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-A48PtXw9.js → index-Yb9EfUnY.js} +1 -1
  129. package/src/assets/web-panel/assets/index-aCu2gbUb.js +1 -0
  130. package/src/assets/web-panel/assets/{index-CzKmteNS.js → index-mTK_ZHhE.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BCqxyvqp.js → index-nio98wGC.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-Db1cLWir.js → index-t6hXF2Cw.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-MCB7-raZ.js → initDefaultProps-GTwJVIOz.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-UWDF9swV.js → motion-BJQLsPx8.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-CsTmgXZU.js → move-DVQM0bdG.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-fQy6KGLg.js → mtc-parser-CMXI1I2y.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-woPnEjd1.js → omit-CAc8MDw9.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-Bs2dZ1gf.js → pickAttrs-BgqzoyP7.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-C7EVgPkg.js → placementArrow-B9ebCdRy.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-BH2kYCTN.js → responsiveObserve-Dy8D0D3n.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-BFBP2V_e.js → slide-Bcrc8J39.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-xdDxDYse.js → statusUtils-CDW6FOlc.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-aAWgPPQf.js → styleChecker-CWhJrDON.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-DkGSAWoO.js → useFlexGapSupport-C82c33XI.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-BrOih40N.js → useFs-A6m--mih.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-eIYrgMqO.js → usePersonalDataHub-m-1laJY5.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-B8lutNSE.js → vnode-CL9oKKRa.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-CffNre7V.js → zoom-DrOyaof3.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agenda.js +71 -14
  151. package/src/commands/agent.js +14 -0
  152. package/src/commands/doctor.js +85 -1
  153. package/src/commands/plugin.js +226 -9
  154. package/src/commands/review.js +17 -1
  155. package/src/commands/session.js +191 -0
  156. package/src/commands/team.js +19 -1
  157. package/src/data/changelog.json +10 -4
  158. package/src/gateways/ws/session-protocol.js +50 -0
  159. package/src/gateways/ws/ws-agent-handler.js +50 -0
  160. package/src/harness/jsonl-session-store.js +60 -0
  161. package/src/harness/worktree-isolator.js +140 -5
  162. package/src/lib/agent-schedule-store.js +30 -1
  163. package/src/lib/agent-team/team-worktree.js +37 -2
  164. package/src/lib/agent-tool-admission.js +125 -0
  165. package/src/lib/app-preview.js +360 -0
  166. package/src/lib/async-hook-supervisor.cjs +54 -0
  167. package/src/lib/capability-manifest.js +319 -0
  168. package/src/lib/capability-negotiation.js +23 -12
  169. package/src/lib/diagnostic-bundle.js +230 -0
  170. package/src/lib/doctor-checkup.js +223 -4
  171. package/src/lib/execution-location.js +286 -0
  172. package/src/lib/headless-manifest.js +13 -40
  173. package/src/lib/hook-stats-store.cjs +197 -0
  174. package/src/lib/idempotency.js +109 -0
  175. package/src/lib/jsonl-session-store.js +1 -0
  176. package/src/lib/lsp/__tests__/lsp-manager.test.js +58 -0
  177. package/src/lib/lsp/lsp-manager.js +56 -0
  178. package/src/lib/monitor-event.js +290 -0
  179. package/src/lib/operation-fingerprint.js +217 -0
  180. package/src/lib/plugin-runtime/bin.js +37 -0
  181. package/src/lib/plugin-runtime/capabilities.js +19 -0
  182. package/src/lib/plugin-runtime/capability-consent.js +28 -0
  183. package/src/lib/plugin-runtime/hooks.js +37 -0
  184. package/src/lib/plugin-runtime/lsp.js +38 -0
  185. package/src/lib/plugin-runtime/mcp.js +40 -0
  186. package/src/lib/plugin-runtime/monitors.js +37 -0
  187. package/src/lib/plugin-runtime/plugin-options.js +151 -0
  188. package/src/lib/pr-automation-policy.js +224 -0
  189. package/src/lib/project-instructions.js +9 -1
  190. package/src/lib/remote-approval-bridge.js +66 -13
  191. package/src/lib/repl-rewind.js +101 -0
  192. package/src/lib/review-comment-anchor.js +241 -0
  193. package/src/lib/review-pipeline.js +46 -7
  194. package/src/lib/runtime-checkup.js +221 -0
  195. package/src/lib/secret-scan.js +409 -0
  196. package/src/lib/session-branch.js +0 -0
  197. package/src/lib/side-effect-ledger.js +105 -0
  198. package/src/lib/skill-loader.js +32 -0
  199. package/src/lib/skill-path-scope.js +63 -0
  200. package/src/lib/sub-agent-context.js +36 -4
  201. package/src/lib/sub-agent-registry.js +19 -0
  202. package/src/lib/telemetry/span-recorder.js +17 -4
  203. package/src/lib/terminal-context.js +195 -0
  204. package/src/lib/unattended-action-policy.js +182 -0
  205. package/src/lib/worktree-cleanup-safety.js +138 -0
  206. package/src/lib/worktree-isolator.js +8 -0
  207. package/src/lib/worktree-sparse.js +131 -0
  208. package/src/repl/agent-repl.js +169 -40
  209. package/src/runtime/agent-core.js +220 -24
  210. package/src/runtime/diagnostics.js +60 -0
  211. package/src/runtime/headless-runner.js +135 -2
  212. package/src/runtime/headless-stream.js +10 -1
  213. package/src/runtime/policies/agent-policy.js +5 -0
  214. package/src/runtime/system-prompt.js +21 -8
  215. package/src/assets/web-panel/assets/ChatBubbleRenderer-DDPd9qT0.js +0 -1
  216. package/src/assets/web-panel/assets/MarkdownRenderer-BqYwEYou.js +0 -1
  217. package/src/assets/web-panel/assets/Terminal-4D8tNBO5.js +0 -3
  218. package/src/assets/web-panel/assets/devWarning-DeIrCfH-.js +0 -1
  219. package/src/assets/web-panel/assets/index-BxUKMRpP.js +0 -1
  220. package/src/assets/web-panel/assets/index-DscU9iPI.js +0 -1
@@ -26,6 +26,7 @@ import sharedHookRunner from "../lib/hook-runner.cjs";
26
26
  import sharedHookEvents from "../lib/settings-hook-events.cjs";
27
27
  import { mergeProviderOptions } from "../lib/provider-options.js";
28
28
  import { applyCredentialProxy } from "../lib/credential-proxy.js";
29
+ import { describeBackgroundCommand } from "../lib/terminal-context.js";
29
30
  import { buildTelemetryAttributes } from "../lib/telemetry-ids.js";
30
31
  import { getPlanModeManager } from "../lib/plan-mode.js";
31
32
  import { CLISkillLoader } from "../lib/skill-loader.js";
@@ -45,6 +46,11 @@ import {
45
46
  import { createToolContext } from "../tools/tool-context.js";
46
47
  import { createToolTelemetryRecord } from "../tools/tool-telemetry.js";
47
48
  import { isAbortError, throwIfAborted } from "../lib/abort-utils.js";
49
+ import {
50
+ classifyEditReplay,
51
+ editIdempotencyKey,
52
+ EDIT_REPLAY,
53
+ } from "../lib/idempotency.js";
48
54
  import { buildSearchCommand } from "../lib/search-command.js";
49
55
  import { discoverCommands } from "../lib/slash-commands.js";
50
56
  import {
@@ -138,18 +144,37 @@ function _readBgStream(stream) {
138
144
  }
139
145
 
140
146
  /**
141
- * Snapshot of background shell tasks (for REPL/host status surfaces).
142
- * @returns {Array<{id:string,status:string,command:string,exitCode:number|null}>}
147
+ * Snapshot of background shell tasks (for REPL/host status surfaces AND the
148
+ * model-facing `check_shell { list:true }` tool).
149
+ *
150
+ * P1-2 (terminal-context): the `command` string can carry an inline secret
151
+ * (`FOO=token cmd`, `curl -H "Authorization: Bearer …"`); this list is shown to
152
+ * the model and rendered in `/tasks`, so run it through
153
+ * `describeBackgroundCommand` to secret-redact the command and expose the PID.
154
+ * The task's own lifecycle `status` (running/exited/failed/error) is preserved
155
+ * — that is distinct from a health check, so it is NOT replaced. `stoppable` is
156
+ * gated on the task still running (a valid pid alone doesn't make a dead task
157
+ * stoppable).
158
+ * @returns {Array<{id:string,status:string,command:string,pid:number|null,
159
+ * stoppable:boolean,exitCode:number|null,startedAt:string,endedAt:string}>}
143
160
  */
144
161
  export function listBackgroundShellTasks() {
145
- return Array.from(_backgroundShellTasks.values()).map((t) => ({
146
- id: t.id,
147
- status: t.status,
148
- command: t.command,
149
- exitCode: t.exitCode,
150
- startedAt: t.startedAt,
151
- endedAt: t.endedAt,
152
- }));
162
+ return Array.from(_backgroundShellTasks.values()).map((t) => {
163
+ const desc = describeBackgroundCommand({
164
+ command: t.command,
165
+ pid: t.child?.pid,
166
+ });
167
+ return {
168
+ id: t.id,
169
+ status: t.status,
170
+ command: desc.command, // P1-2: secret-redacted
171
+ pid: desc.pid, // P1-2: PID surfaced
172
+ stoppable: t.status === "running" && desc.stoppable,
173
+ exitCode: t.exitCode,
174
+ startedAt: t.startedAt,
175
+ endedAt: t.endedAt,
176
+ };
177
+ });
153
178
  }
154
179
 
155
180
  // Kill a background task's whole process tree. Because tasks are spawned with
@@ -826,20 +851,38 @@ export function buildSystemPrompt(cwd, opts = {}) {
826
851
  // Non-critical — skill loader may not be available
827
852
  }
828
853
 
829
- // Append rules.md
830
- try {
831
- const projectRoot = findProjectRoot(dir);
832
- if (projectRoot) {
833
- const rulesPath = path.join(projectRoot, ".chainlesschain", "rules.md");
834
- if (fs.existsSync(rulesPath)) {
835
- const content = fs.readFileSync(rulesPath, "utf-8");
836
- if (content.trim()) {
837
- prompt += `\n\n## Project Rules\n${content}`;
854
+ // Append rules.md — unless the caller opted into a lean/off prompt. `rules.md`
855
+ // is coding-convention DETAIL (the entry cc.md/CLAUDE.md references it), so it
856
+ // is shed both when project memory is fully off (`--no-project-memory` →
857
+ // projectMemory === false) AND in entry-only lean mode (projectMemory ===
858
+ // "lean", or the env signal CC_PROJECT_MEMORY=lean when no explicit value is
859
+ // threaded). NOTE: legacy `CC_PROJECT_MEMORY=0` intentionally still KEEPS
860
+ // rules.md (its long-standing contract only dropped the instruction block) —
861
+ // so we only honor the "lean" env here, never "0".
862
+ const _pm = opts.projectMemory;
863
+ const _envLean =
864
+ process.env.CC_PROJECT_MEMORY === "lean" ||
865
+ process.env.CC_PROJECT_MEMORY === "entry";
866
+ const _dropRules =
867
+ _pm === false ||
868
+ _pm === "lean" ||
869
+ _pm === "entry" ||
870
+ (_pm == null && _envLean);
871
+ if (!_dropRules) {
872
+ try {
873
+ const projectRoot = findProjectRoot(dir);
874
+ if (projectRoot) {
875
+ const rulesPath = path.join(projectRoot, ".chainlesschain", "rules.md");
876
+ if (fs.existsSync(rulesPath)) {
877
+ const content = fs.readFileSync(rulesPath, "utf-8");
878
+ if (content.trim()) {
879
+ prompt += `\n\n## Project Rules\n${content}`;
880
+ }
838
881
  }
839
882
  }
883
+ } catch {
884
+ // Non-critical
840
885
  }
841
- } catch {
842
- // Non-critical
843
886
  }
844
887
 
845
888
  // Advertise extra workspace roots (--add-dir) so the model knows it may
@@ -2100,6 +2143,26 @@ async function _withPostEditDiagnostics(result, filePath, cwd) {
2100
2143
  return newDiagnostics ? { ...result, newDiagnostics } : result;
2101
2144
  }
2102
2145
 
2146
+ /**
2147
+ * Drop skills whose `paths:` frontmatter scopes them to a different subtree than
2148
+ * the agent's cwd (large-monorepo lazy skill surface). A skill with no `paths`
2149
+ * applies everywhere, so a skill set with none is returned unchanged. Best-effort:
2150
+ * any failure (or an unlocatable project root → relCwd "") falls open to the full
2151
+ * list, never hiding a skill spuriously.
2152
+ */
2153
+ async function filterSkillsByCwd(skills, cwd) {
2154
+ try {
2155
+ if (!Array.isArray(skills) || skills.every((s) => !s?.paths)) return skills;
2156
+ const { filterSkillsByRelCwd, relCwdForCwd } =
2157
+ await import("../lib/skill-path-scope.js");
2158
+ const { findProjectRoot } = await import("../lib/project-detector.js");
2159
+ const root = (cwd && findProjectRoot(cwd)) || cwd || "";
2160
+ return filterSkillsByRelCwd(skills, relCwdForCwd(cwd, root));
2161
+ } catch {
2162
+ return skills; // fail-open: never hide a skill because scoping errored
2163
+ }
2164
+ }
2165
+
2103
2166
  async function executeToolInner(
2104
2167
  name,
2105
2168
  args,
@@ -2323,6 +2386,30 @@ async function executeToolInner(
2323
2386
  replaceAll,
2324
2387
  );
2325
2388
  if (count === 0) {
2389
+ // Idempotent replay (P0-2 "Diff Apply 内容哈希"): a resumed edit whose
2390
+ // old_string is already gone but whose new_string is present already
2391
+ // landed. Report a no-op success (NO write occurs — zero data risk)
2392
+ // rather than a misleading "not found" error, so a recovered worker
2393
+ // doesn't get stuck re-issuing an edit that already happened.
2394
+ const replay = classifyEditReplay({
2395
+ content,
2396
+ oldString: args.old_string,
2397
+ newString: args.new_string,
2398
+ });
2399
+ if (replay === EDIT_REPLAY.ALREADY_APPLIED) {
2400
+ return attachDescriptor({
2401
+ success: true,
2402
+ path: filePath,
2403
+ alreadyApplied: true,
2404
+ idempotencyKey: editIdempotencyKey({
2405
+ path: args.path,
2406
+ oldString: args.old_string,
2407
+ newString: args.new_string,
2408
+ replaceAll,
2409
+ }),
2410
+ note: "edit already applied (new_string present, old_string absent) — no change made",
2411
+ });
2412
+ }
2326
2413
  return attachDescriptor({ error: "old_string not found in file" });
2327
2414
  }
2328
2415
  // Require a UNIQUE match (Claude-Code Edit parity) — replacing the first
@@ -3220,6 +3307,16 @@ async function executeToolInner(
3220
3307
  goalMaxTokens: args.goal_max_tokens ?? null,
3221
3308
  goalMaxCost: args.goal_max_cost ?? null,
3222
3309
  goalMaxTime: args.goal_max_time ?? null,
3310
+ // P1-8: action classes this unattended task may still perform (e.g.
3311
+ // "publish", "external_message"). Accepts an array or a comma list.
3312
+ unattendedAllowlist: Array.isArray(args.unattended_allow)
3313
+ ? args.unattended_allow
3314
+ : typeof args.unattended_allow === "string"
3315
+ ? args.unattended_allow
3316
+ .split(",")
3317
+ .map((s) => s.trim())
3318
+ .filter(Boolean)
3319
+ : null,
3223
3320
  };
3224
3321
  if (action === "wakeup") {
3225
3322
  if (!args.prompt) {
@@ -3620,7 +3717,10 @@ async function executeToolInner(
3620
3717
  }
3621
3718
 
3622
3719
  case "run_skill": {
3623
- const allSkills = skillLoader.getResolvedSkills().filter(skillAllowed);
3720
+ const allSkills = await filterSkillsByCwd(
3721
+ skillLoader.getResolvedSkills().filter(skillAllowed),
3722
+ cwd,
3723
+ );
3624
3724
  if (allSkills.length === 0) {
3625
3725
  return attachDescriptor({
3626
3726
  error: _skillAllowlist
@@ -3766,7 +3866,10 @@ async function executeToolInner(
3766
3866
  }
3767
3867
 
3768
3868
  case "list_skills": {
3769
- let skills = skillLoader.getResolvedSkills().filter(skillAllowed);
3869
+ let skills = await filterSkillsByCwd(
3870
+ skillLoader.getResolvedSkills().filter(skillAllowed),
3871
+ cwd,
3872
+ );
3770
3873
  if (skills.length === 0) {
3771
3874
  return attachDescriptor({
3772
3875
  error: _skillAllowlist
@@ -6379,6 +6482,22 @@ function extractInitialPromptText(messages) {
6379
6482
  return "";
6380
6483
  }
6381
6484
 
6485
+ /**
6486
+ * Stable id for a permission GATE decision, so a denied/gated tool span can be
6487
+ * correlated with the decision that blocked it. Only gated results carry a
6488
+ * `policy` (allow-path tools execute with no distinct decision), so this returns
6489
+ * null when there's nothing to identify. Deterministic (call id + gate) — no
6490
+ * clock/RNG — so a consumer holding the tool-result's policy + tool_call_id can
6491
+ * recompute it. Not exported (no shim-parity impact) — only the telemetry seam
6492
+ * uses it.
6493
+ */
6494
+ function permissionDecisionId(callId, policy) {
6495
+ if (!policy || typeof policy !== "object" || !policy.decision) return null;
6496
+ const gate = policy.via || policy.decision;
6497
+ const base = callId || "call";
6498
+ return `${base}:perm:${gate}`;
6499
+ }
6500
+
6382
6501
  export async function* agentLoop(messages, options) {
6383
6502
  // Shared iteration budget — replaces hardcoded MAX_ITERATIONS.
6384
6503
  // When options.iterationBudget is provided (e.g. from parent agent),
@@ -6858,6 +6977,18 @@ export async function* agentLoop(messages, options) {
6858
6977
  }
6859
6978
  }
6860
6979
 
6980
+ // Per-span unified ids (P2 observability): the run-level default attributes
6981
+ // stamp session/agent/workflow ids on every span, but turn/prompt ids are
6982
+ // per-iteration. A "turn" is one model iteration of the run; "prompt" is the
6983
+ // model request within that turn. Normalized through buildTelemetryAttributes
6984
+ // so they get the same charset-sanitized + cardinality-bounded treatment as
6985
+ // the run-level ids (turn.id correlates with the agent.iteration counter).
6986
+ const modelIdAttrs = recorder
6987
+ ? buildTelemetryAttributes({
6988
+ turnId: `${runId}:t${budget.consumed}`,
6989
+ promptId: `${runId}:t${budget.consumed}:p`,
6990
+ })
6991
+ : null;
6861
6992
  const result = await _withSpan(
6862
6993
  recorder,
6863
6994
  "agent.model",
@@ -6865,6 +6996,7 @@ export async function* agentLoop(messages, options) {
6865
6996
  "gen_ai.system": options.provider || "ollama",
6866
6997
  "gen_ai.request.model": options.model || "unknown",
6867
6998
  "agent.iteration": budget.consumed,
6999
+ ...(modelIdAttrs || {}),
6868
7000
  },
6869
7001
  () => llmCall(callMessages, options),
6870
7002
  (span, r) => {
@@ -6886,6 +7018,21 @@ export async function* agentLoop(messages, options) {
6886
7018
  Array.isArray(r?.message?.tool_calls) &&
6887
7019
  r.message.tool_calls.length > 0,
6888
7020
  );
7021
+ // response CONTENT is opt-in (--otlp-content): stamped only when
7022
+ // enabled, redacted + length-capped through the same normalizer; the
7023
+ // field is entirely absent by default so default OTLP stays unchanged.
7024
+ if (options.otlpIncludeContent === true && r?.message?.content) {
7025
+ const respAttrs = buildTelemetryAttributes(
7026
+ { response: r.message.content },
7027
+ { includeContent: true },
7028
+ );
7029
+ if (respAttrs["content.response"] != null) {
7030
+ span.setAttribute(
7031
+ "content.response",
7032
+ respAttrs["content.response"],
7033
+ );
7034
+ }
7035
+ }
6889
7036
  },
6890
7037
  "model_error",
6891
7038
  );
@@ -7163,17 +7310,66 @@ export async function* agentLoop(messages, options) {
7163
7310
 
7164
7311
  let toolResult;
7165
7312
  let toolError = null;
7313
+ // Per-span unified ids (P2 observability): a tool span carries its turn.id
7314
+ // (correlating with the model span of the same iteration), the provider's
7315
+ // tool_use.id (so a tool-result event can be tied back to its call), and —
7316
+ // when auto-checkpoint fired before a mutating tool — the checkpoint.id the
7317
+ // user could restore to. All normalized through buildTelemetryAttributes.
7318
+ // tool_arguments CONTENT is opt-in (--otlp-content): the alias key is only
7319
+ // present when explicitly enabled, so by default the field is omitted
7320
+ // entirely (byte-identical default OTLP), and even opted-in it's length-
7321
+ // capped by redactContent. Mirrors the run-level content.prompt opt-in.
7322
+ const toolContentOptIn = options.otlpIncludeContent === true;
7323
+ const toolIdAttrs = recorder
7324
+ ? buildTelemetryAttributes(
7325
+ {
7326
+ turnId: `${runId}:t${budget.consumed}`,
7327
+ toolUseId: call.id,
7328
+ checkpointId: cpId || undefined,
7329
+ ...(toolContentOptIn ? { toolArguments: toolArgs } : {}),
7330
+ },
7331
+ { includeContent: toolContentOptIn },
7332
+ )
7333
+ : null;
7166
7334
  try {
7167
7335
  toolResult = await _withSpan(
7168
7336
  recorder,
7169
7337
  "agent.tool",
7170
- { "tool.name": toolName },
7338
+ { "tool.name": toolName, ...(toolIdAttrs || {}) },
7171
7339
  () => executeTool(toolName, toolArgs, toolContext),
7172
7340
  (span, r) => {
7173
7341
  span.setAttribute(
7174
7342
  "tool.is_error",
7175
7343
  !!(r && typeof r === "object" && r.error),
7176
7344
  );
7345
+ // permission.decision_id (P2): a GATED tool result carries a
7346
+ // `policy` (deny / ask-fail / host-block / sandbox); allow-path
7347
+ // tools execute with no distinct decision. Stamp a stable id
7348
+ // (derived from the call + gate, so it's recomputable from the
7349
+ // tool-result's policy + tool_call_id) plus the low-cardinality
7350
+ // decision, letting a blocked tool span be tied to its decision.
7351
+ const decId =
7352
+ r && typeof r === "object"
7353
+ ? permissionDecisionId(call.id, r.policy)
7354
+ : null;
7355
+ if (decId) {
7356
+ const permAttrs = buildTelemetryAttributes({
7357
+ decisionId: decId,
7358
+ "permission.decision": r.policy.decision,
7359
+ });
7360
+ if (permAttrs["permission.decision_id"]) {
7361
+ span.setAttribute(
7362
+ "permission.decision_id",
7363
+ permAttrs["permission.decision_id"],
7364
+ );
7365
+ }
7366
+ if (permAttrs["permission.decision"]) {
7367
+ span.setAttribute(
7368
+ "permission.decision",
7369
+ permAttrs["permission.decision"],
7370
+ );
7371
+ }
7372
+ }
7177
7373
  },
7178
7374
  "tool_error",
7179
7375
  );
@@ -288,6 +288,66 @@ export async function collectDoctorReport() {
288
288
  };
289
289
  }
290
290
 
291
+ /**
292
+ * Parse `git worktree list --porcelain` output into `{ path, branch }` entries.
293
+ *
294
+ * Pure (no I/O) so it can be unit-tested against fixed fixtures. A detached
295
+ * worktree has no `branch` line and yields `branch: null`.
296
+ */
297
+ export function parseGitWorktrees(porcelain) {
298
+ if (!porcelain || typeof porcelain !== "string") return [];
299
+ const entries = [];
300
+ let cur = null;
301
+ for (const line of porcelain.split("\n")) {
302
+ const trimmed = line.trimEnd();
303
+ if (trimmed.startsWith("worktree ")) {
304
+ if (cur) entries.push(cur);
305
+ cur = { path: trimmed.slice("worktree ".length).trim(), branch: null };
306
+ } else if (trimmed.startsWith("branch ") && cur) {
307
+ cur.branch = trimmed
308
+ .slice("branch ".length)
309
+ .replace("refs/heads/", "")
310
+ .trim();
311
+ }
312
+ }
313
+ if (cur) entries.push(cur);
314
+ return entries;
315
+ }
316
+
317
+ /** Best-effort live git worktree list for the current repo. */
318
+ export function collectWorktrees() {
319
+ return parseGitWorktrees(safeExec("git worktree list --porcelain"));
320
+ }
321
+
322
+ /**
323
+ * Map a doctor report + ambient runtime signals into the input contract of
324
+ * `buildDiagnosticBundle` (src/lib/diagnostic-bundle.js).
325
+ *
326
+ * Pure & deterministic aside from reading this process's own platform tags:
327
+ * the caller gathers live signals (env, worktrees, lockfiles) and passes them
328
+ * in, so the mapping itself is unit-testable. Session-runtime fields
329
+ * (connectionState, reconnectHistory, trace, redactionEvents) are absent from a
330
+ * cold `cc doctor` invocation and left to the bundle builder's safe defaults.
331
+ */
332
+ export function buildDoctorDiagnosticInput(report, signals = {}) {
333
+ const r = report || {};
334
+ return {
335
+ version: r.version || null,
336
+ platform: {
337
+ os: process.platform,
338
+ arch: process.arch,
339
+ node: process.versions?.node || null,
340
+ },
341
+ ports: (Array.isArray(r.ports) ? r.ports : [])
342
+ .filter((p) => p && p.open)
343
+ .map((p) => ({ port: p.port, proto: p.name })),
344
+ worktrees: Array.isArray(signals.worktrees) ? signals.worktrees : [],
345
+ lockfiles: Array.isArray(signals.lockfiles) ? signals.lockfiles : [],
346
+ env: signals.env && typeof signals.env === "object" ? signals.env : {},
347
+ notes: signals.notes != null ? signals.notes : null,
348
+ };
349
+ }
350
+
291
351
  /**
292
352
  * Collect a machine-readable status report.
293
353
  *
@@ -51,6 +51,13 @@ import {
51
51
  getLastSessionId as jsonlGetLastSessionId,
52
52
  verifySession as jsonlVerifySession,
53
53
  } from "../harness/jsonl-session-store.js";
54
+ import {
55
+ SideEffectLedger,
56
+ reconcileSideEffects,
57
+ classifyToolSideEffect,
58
+ } from "../lib/side-effect-ledger.js";
59
+ import { SIDE_EFFECT_LEDGER_EVENT } from "../lib/side-effect-ledger-store.js";
60
+ import { operationIdempotencyKey } from "../lib/idempotency.js";
54
61
  import { expandFileRefsAsync } from "./file-ref-expander.js";
55
62
  import { composeSystemPrompt } from "./system-prompt.js";
56
63
  import { buildUserContent } from "../lib/image-input.js";
@@ -688,6 +695,81 @@ export async function runAgentHeadless(options = {}, deps = {}) {
688
695
  }
689
696
  }
690
697
 
698
+ // ── P0-2: crash-safe side-effect ledger ────────────────────────────────
699
+ // Dangerous tools (file writes, opaque shell, git push, publish/schedule/
700
+ // notify/browser actions) are recorded prepare→start→commit|fail and the
701
+ // full snapshot persisted before the effect settles, so a worker killed
702
+ // mid-flight does NOT blindly replay an effect that may already have landed.
703
+ // Only active when persisting (an ephemeral/one-shot run can't be resumed);
704
+ // the byte-for-byte default path is untouched when no dangerous tool runs.
705
+ const sideEffectRunNonce = String(deps.now ? deps.now() : Date.now());
706
+ let sideEffectLedger = new SideEffectLedger({ clock: deps.now || null });
707
+ let sideEffectSeq = 0;
708
+ let currentSideEffectOpId = null;
709
+ let resumeSideEffectContext = null;
710
+ const persistSideEffectLedger = () => {
711
+ if (!persist) return;
712
+ try {
713
+ store.appendEvent(
714
+ sessionId,
715
+ SIDE_EFFECT_LEDGER_EVENT,
716
+ sideEffectLedger.toJSON(),
717
+ );
718
+ } catch {
719
+ // best-effort — never fail the run over ledger persistence
720
+ }
721
+ };
722
+ if (persist) {
723
+ try {
724
+ const events = store.readEvents(sessionId) || [];
725
+ for (let i = events.length - 1; i >= 0; i--) {
726
+ const e = events[i];
727
+ if (
728
+ e &&
729
+ e.type === SIDE_EFFECT_LEDGER_EVENT &&
730
+ e.data &&
731
+ Array.isArray(e.data.ops)
732
+ ) {
733
+ sideEffectLedger = SideEffectLedger.fromJSON(e.data, {
734
+ clock: deps.now || null,
735
+ });
736
+ break;
737
+ }
738
+ }
739
+ } catch {
740
+ // fresh ledger — best effort
741
+ }
742
+ // On resume, surface any operation that was in flight when the prior run
743
+ // died: its outcome is UNKNOWN, so the model is told to VERIFY before any
744
+ // replay rather than silently re-issue an irreversible effect.
745
+ if (resumeId) {
746
+ try {
747
+ const plan = reconcileSideEffects(sideEffectLedger);
748
+ if (plan.inspect.length > 0) {
749
+ const lines = plan.plans
750
+ .filter((p) => p.action === "inspect")
751
+ .map((p) => {
752
+ const op = sideEffectLedger.get(p.opId);
753
+ const kind = op?.kind || "unknown";
754
+ const key = op?.key ? ` (${op.key})` : "";
755
+ return ` • [${kind}]${key} — ${p.reason}`;
756
+ });
757
+ resumeSideEffectContext =
758
+ "Recovery notice — the previous run was interrupted while these " +
759
+ "irreversible operations were in flight; their outcome is UNKNOWN. " +
760
+ "Do NOT blindly re-run them. Verify whether each already took " +
761
+ "effect before repeating it, and ask the user if unsure:\n" +
762
+ lines.join("\n");
763
+ writeErr(
764
+ `⚠ ${plan.inspect.length} interrupted side-effect(s) need verification before replay (resume ${resumeId}).\n`,
765
+ );
766
+ }
767
+ } catch {
768
+ resumeSideEffectContext = null;
769
+ }
770
+ }
771
+ }
772
+
691
773
  // ── Wire the persistent ApprovalGate with our non-interactive confirmer
692
774
  // and force the session-policy tier dictated by --permission-mode. ──────
693
775
  let approvalGate = null;
@@ -770,13 +852,21 @@ export async function runAgentHeadless(options = {}, deps = {}) {
770
852
  }
771
853
  }
772
854
 
855
+ // --no-project-memory (options.projectMemory === false): lean prompt — skip
856
+ // rules.md (in buildSystemPrompt) + the cc.md/CLAUDE.md block. Absent flag
857
+ // (undefined) leaves both paths byte-identical.
858
+ const _leanNoProjectMemory = options.projectMemory === false;
773
859
  const systemContent = composeSystemPrompt(
774
- buildSystemPrompt(cwd, { additionalDirectories }),
860
+ buildSystemPrompt(cwd, {
861
+ additionalDirectories,
862
+ projectMemory: options.projectMemory,
863
+ }),
775
864
  {
776
865
  systemPrompt: options.systemPrompt,
777
866
  appendSystemPrompt: options.appendSystemPrompt,
778
867
  outputStyle: outputStyleBody,
779
868
  instructionExcludes,
869
+ projectMemory: _leanNoProjectMemory ? false : undefined,
780
870
  },
781
871
  );
782
872
 
@@ -863,6 +953,9 @@ export async function runAgentHeadless(options = {}, deps = {}) {
863
953
  ...(sessionStartContext
864
954
  ? [{ role: "system", content: sessionStartContext }]
865
955
  : []),
956
+ ...(resumeSideEffectContext
957
+ ? [{ role: "system", content: resumeSideEffectContext }]
958
+ : []),
866
959
  ...history,
867
960
  { role: "user", content: userMessageContent },
868
961
  ]);
@@ -1067,7 +1160,7 @@ export async function runAgentHeadless(options = {}, deps = {}) {
1067
1160
  try {
1068
1161
  const { AsyncHookSupervisor } =
1069
1162
  await import("../lib/async-hook-supervisor.cjs");
1070
- _hookSupervisor = new AsyncHookSupervisor();
1163
+ _hookSupervisor = new AsyncHookSupervisor({ persistStats: true });
1071
1164
  } catch {
1072
1165
  _hookSupervisor = null; // async hooks are best-effort
1073
1166
  }
@@ -1492,10 +1585,50 @@ export async function runAgentHeadless(options = {}, deps = {}) {
1492
1585
  args: event.args,
1493
1586
  });
1494
1587
  toolCalls.push({ tool: event.tool, args: event.args });
1588
+ // P0-2: record an irreversible effect as STARTED (persisted before
1589
+ // it settles) so a crash before the matching tool-result leaves a
1590
+ // reconcilable "in flight" marker instead of a silent replay.
1591
+ currentSideEffectOpId = null;
1592
+ if (persist) {
1593
+ const se = classifyToolSideEffect(event.tool, event.args);
1594
+ if (se) {
1595
+ const opId = `${sideEffectRunNonce}:${sideEffectSeq++}`;
1596
+ currentSideEffectOpId = opId;
1597
+ sideEffectLedger
1598
+ .prepare(opId, {
1599
+ kind: se.kind,
1600
+ key: se.key,
1601
+ // Content-addressed key: a resumed replay of the SAME effect
1602
+ // derives the SAME key, so an external provider can de-dupe
1603
+ // and countDuplicateCommittedEffects can measure `0` repeats.
1604
+ meta: {
1605
+ tool: event.tool,
1606
+ idempotencyKey: operationIdempotencyKey({
1607
+ tool: event.tool,
1608
+ args: event.args,
1609
+ }),
1610
+ },
1611
+ })
1612
+ .start(opId);
1613
+ persistSideEffectLedger();
1614
+ }
1615
+ }
1495
1616
  break;
1496
1617
  }
1497
1618
  case "tool-result": {
1498
1619
  const err = event.error || event.result?.error || null;
1620
+ // P0-2: settle the in-flight side-effect (commit on success, fail on
1621
+ // a clean error) and persist the updated ledger snapshot.
1622
+ if (persist && currentSideEffectOpId) {
1623
+ if (err)
1624
+ sideEffectLedger.fail(
1625
+ currentSideEffectOpId,
1626
+ String(err).slice(0, 200),
1627
+ );
1628
+ else sideEffectLedger.commit(currentSideEffectOpId);
1629
+ persistSideEffectLedger();
1630
+ currentSideEffectOpId = null;
1631
+ }
1499
1632
  if (isText && err) writeErr(` Error: ${err}\n`);
1500
1633
  emitStream({
1501
1634
  type: "tool_result",
@@ -1073,8 +1073,16 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
1073
1073
  }
1074
1074
  }
1075
1075
 
1076
+ // --no-project-memory (options.projectMemory === false) → lean system prompt:
1077
+ // skip both the rules.md append inside buildSystemPrompt AND the auto-loaded
1078
+ // cc.md/CLAUDE.md instruction block. Only `=== false` changes anything; when
1079
+ // the flag is absent (undefined) both paths stay byte-identical.
1080
+ const _leanNoProjectMemory = options.projectMemory === false;
1076
1081
  const systemContent = composeSystemPrompt(
1077
- buildSystemPrompt(cwd, { additionalDirectories }),
1082
+ buildSystemPrompt(cwd, {
1083
+ additionalDirectories,
1084
+ projectMemory: options.projectMemory,
1085
+ }),
1078
1086
  {
1079
1087
  systemPrompt: options.systemPrompt,
1080
1088
  appendSystemPrompt: _jsonSchema
@@ -1084,6 +1092,7 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
1084
1092
  : options.appendSystemPrompt,
1085
1093
  outputStyle: outputStyleBody,
1086
1094
  instructionExcludes,
1095
+ projectMemory: _leanNoProjectMemory ? false : undefined,
1087
1096
  },
1088
1097
  );
1089
1098
  const messages = [{ role: "system", content: systemContent }];
@@ -49,6 +49,11 @@ export function resolveAgentPolicy({
49
49
  // before 2026-07-09, breaking --vim/--think/--thinking-budget/
50
50
  // --fallback-model/--pdh/--permission-mode for interactive sessions.
51
51
  permissionMode: overrides.permissionMode,
52
+ // --no-project-memory tri-state (undefined=default-on / false=lean prompt).
53
+ // Plain passthrough consumed by startAgentRepl → composeSystemPrompt /
54
+ // buildSystemPrompt; without this allowlist entry the flag would be silently
55
+ // dropped for interactive sessions (same class as the vim/think/pdh fixes).
56
+ projectMemory: overrides.projectMemory,
52
57
  vimMode: overrides.vimMode,
53
58
  thinking: overrides.thinking,
54
59
  thinkingBudget: overrides.thinkingBudget,