chainlesschain 0.162.162 → 0.162.164

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 (237) hide show
  1. package/package.json +3 -1
  2. package/src/assets/web-panel/assets/{AIOps-ClI2ALgv.js → AIOps-BqRkaHGb.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BDjRcJaK.js → ActionButton-DWO7OZLC.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DikSRsaT.js → Analytics-C5oaVezM.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-9mLhMFmz.js → AppLayout-CsUESDoZ.js} +3 -3
  6. package/src/assets/web-panel/assets/{Artifacts-BSr88BrH.js → Artifacts-Dragqu5k.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-CdvyimOF.js → Audit-CQsaHctY.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-D3_hCedh.js → BackgroundAgents-DdG1jNSM.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-Cyvv9RHf.js → Backup-DN6sjK4z.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-BZM14kt8.js → BaseInput-Cxm_ZTCl.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-DrQSokVz.js → Chat-DVXGmA56.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-HwJR-C6B.js → ChatBubbleRenderer-BdaCL_iE.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-bFLYeWMV.js → Checkbox-DEc4vD_A.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-7mN7xJXT.js → Codegen-CKiTcv36.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-DAB5ZWm7.js → Col-Br9UUNN0.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-B_1TymaK.js → Community-NeUH_70T.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-Bq1uSWLJ.js → Compact-BqR-z2Df.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-CyiE1jEe.js → Compliance-DcWB_SgZ.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-Cpk0D7vm.js → Cowork-BfhgcudF.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-d_Lv3rdB.js → Cron-LaU29LgD.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DMqhHjMR.js → Crosschain-B2ZB9uwW.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-Br-oPuBw.js → DID-BuHl38A2.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-CfLW_RaP.js → Dashboard-m627yqvZ.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-DZnTOExV.js → Dropdown-C7bxF8F5.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-DHIUHDv1.js → EmailListRenderer-DPLPQzLp.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bw3t3myu.js → FamilyGuardDashboard-cOiH6ike.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-D9cpQPAY.js → Federation-Bvkzg88z.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-Jdczr9Mr.js → FormItemContext-BTsiA7h_.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-BT8ZMpC5.js → GenericCardRenderer-BGnVE5qu.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-DjARip2b.js → Git-DS_j6r25.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-BgWbSRcj.js → Governance-CKRG3Rsl.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-CfouJciM.js → Inference-DW8INoNQ.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-50l7mrAr.js → KnowledgeGraph-BhEYeFhC.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-rq50H84B.js → Logs-wqi0uxuR.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-DUPIEdQH.js → MarkdownRenderer-DS8dE8ty.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-BYgPDmHU.js → Marketplace-CMlTuPLT.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-5DVgcFdx.js → McpTools-CSx4m5JJ.js} +3 -3
  38. package/src/assets/web-panel/assets/{Memory-B70LPk7f.js → Memory-vnQCUy-_.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-De8ssA5Y.js → MobileBridge-BV2NA742.js} +1 -1
  40. package/src/assets/web-panel/assets/MobileProjects-Bw0k8e1B.js +1 -0
  41. package/src/assets/web-panel/assets/{Mtc-RJsOTsIA.js → Mtc-CzK_FPB1.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-Crh-6BEK.js → MtcAudit-Cb7G9oL3.js} +5 -5
  43. package/src/assets/web-panel/assets/{Multisig-wuoR5l3x.js → Multisig-qJxlUyzr.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-U2Mji4fZ.js → NLProgramming-DqaBJ7Te.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-CdTZAyDH.js → Notes-CwhTJkRi.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-C-FucWE7.js → NotificationSettings-YDs4Fal4.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-DCYFTeaI.js → OrderTableRenderer-CvEALpq7.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-an3OF85L.js → Organization-CEmQm9n8.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-C-1vmYFe.js → Overflow-zby_P_LA.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-Cwh47xKg.js → P2P-DsmDNFNT.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-Dm38GT0k.js → PdhVaultBrowser-FWwFlZGF.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-DS2Y8awD.js → Permissions-DuzQ6me5.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-BQNziA0I.js → PersonalDataHub-DZbeN613.js} +3 -3
  54. package/src/assets/web-panel/assets/{Pipeline-DF85L1ue.js → Pipeline-CoNSVAUS.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-BuLornF-.js → Privacy-OYJEAaMC.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-C3Y31IwX.js → ProjectInit-CWIapV--.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-BCPiI8aD.js → ProjectSettings-CX8xMxZn.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-BZ6s4k6g.js → Projects-BLCN8YyY.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-D3Wp022X.js → Providers-BV_sxp75.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-CUJfQkH4.js → QrScannerModal-BTnRrHRp.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-UAc0EO8l.js → QuickAsk-D1KZg018.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-BaxKupcs.js → Recommend-COm5cuNg.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-C75NNnXZ.js → RemoteSession-0saWEkYI.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-BVXynZAk.js → Reputation-Dr7iKLS2.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-C5MRZ0bi.js → Row-C1vpPBcr.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-DAKkEYka.js → RssFeed-CxlWrtNj.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-DvWv9YOA.js → Search-BT2V8dPN.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-B5peafW1.js → Security-bm9V3c5k.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-UQmvPEaG.js → Services-CocH1yyX.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-C3bhWpuF.js → Skeleton-DHItlMdd.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-DfkKf9_H.js → Skills-C59sLc4w.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-DqMSjlBM.js → Sla-Crj4BMc8.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-C-WbxWUX.js → SpeechSettings-DX4KFceY.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-CYuHZ4N2.js → SyncSettings-BD_iwQLr.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-CyeAyB9d.js → Tasks-BmCTzYsH.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-BTCeQnpq.js → Templates-BGUaxZYl.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-BifQuA9Y.js → Tenant-BS0tJx2O.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-Bx_9Bocf.js → Terminal--tHSAsld.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-BX0RFCOm.js → TimelineRenderer-D9-MFxS4.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-CGHrCmUS.js → Tokens-CiCB485f.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-DlqaY4SO.js → Trigger-SmCFuW_l.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-DfOmj-ay.js → Trust-DkK3Rqe0.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-B72jH5fE.js → UkeySign-CDM4LGdB.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-BT-63fqv.js → VideoEditing-IDB7DJXt.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-Dsul3sHr.js → Wallet-Bikj5cmt.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-Izkwwdgy.js → WebAuthn-C9abWlAw.js} +2 -2
  87. package/src/assets/web-panel/assets/{WorkflowEditor-D3FHSiH1.js → WorkflowEditor-DrbRKVky.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-CmVz5z3U.js → chat-CccCSDQf.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-CuLgyJBp.js → colors-CXObXLhb.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-Ml1YkXpw.js → compact-item-B5kfYAxC.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-BvjJ48bs.js → createContext-DmiPBfip.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-CvKrADNa.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DVC0157C.js → hasIn-Q6lYteAc.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-C229ABfy.js → index-25Wlqps4.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Ck_52dz_.js → index-33U29UNV.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-QXzlT9KD.js → index-4oXBDczR.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-4SBta3d7.js → index-BGYXvMc5.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-B0S8NzO6.js → index-BJCCa0s_.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DJyN28Et.js → index-BJJ5DhKI.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DC_OI9H2.js → index-BLOzViJ6.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-C_XuRgGn.js → index-BRrbUMoG.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-wvb_FtaV.js → index-BUFpT17Q.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-KMqhcF7l.js → index-BkRDiqGc.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-vG4XSJ1d.js → index-BsfvW6my.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DYXHFr4r.js → index-By-kfPED.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DuGszaWY.js → index-BzFTcQsc.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CXkJH-lK.js → index-C2FXdboq.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Dt5KMjwJ.js → index-C5XOlTHN.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-4wcgvPoF.js → index-CLhBDS2w.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-C_H_S-DY.js → index-CMq7UeoT.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BydjdZnl.js → index-CVMTWNKl.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BjHBpRYZ.js → index-Caghi-yL.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-Bzr0rB5T.js → index-CdB6FuSH.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BbPtzB8M.js → index-CoK8-kWQ.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BUt8kTIB.js → index-CsO3GUAY.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-C_vRgTl9.js → index-CwZBa20Z.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DukDVXCv.js → index-D-kASucw.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BbepULSc.js → index-D8eBw7vW.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BfuKP4qe.js → index-D9wgifk4.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BiC6dQZG.js → index-DAYCTc07.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CmFNvNLZ.js → index-DF6Q8l2h.js} +1 -1
  122. package/src/assets/web-panel/assets/index-DTdFUWc_.js +1 -0
  123. package/src/assets/web-panel/assets/{index-KjgKdZe-.js → index-DgTLApS9.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-BpFJHiTv.js → index-DgxVJ8pg.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Cw0Rjy87.js → index-DlJvIqoR.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CFYVQMvu.js → index-Dw4eHfsG.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CudmwNBV.js → index-Rh-ytwow.js} +3 -3
  128. package/src/assets/web-panel/assets/{index-DDGaOWBV.js → index-StAEt2pq.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-D3lD5qap.js → index-bzfw951k.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BzG-x9p4.js → index-fSUMbMvv.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-B_EvG_GW.js → index-tFHy5Wol.js} +1 -1
  132. package/src/assets/web-panel/assets/index-zQ0M_kz-.js +1 -0
  133. package/src/assets/web-panel/assets/{initDefaultProps-BDgd2YZ8.js → initDefaultProps-BNq7X72N.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-ErtrXdYe.js → motion-DOf9viTc.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-_GPPNCgs.js → move-CRHTcW4x.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-DRLL-8V2.js → mtc-parser-Cyoozgm2.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-p86sl023.js → omit-6-TjFNru.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-BTu7cMLH.js → pickAttrs-DlUmO1zi.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-BEKz6Isr.js → placementArrow-CFOL8mIR.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-Ck42c9MP.js → responsiveObserve-BtaFK5zd.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-1CEvuPsB.js → slide-BIbc2bur.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-BDfdx1GJ.js → statusUtils-BXX9d6-w.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-B4K46m5w.js → styleChecker-BGv10Bpn.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-DoIVpJNq.js → useFlexGapSupport-bKnKT8tw.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-DdAWi-ln.js → useFs-FY4IL4jn.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-iUu6k8dx.js → usePersonalDataHub-Bwl9TEgu.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-DZz4R6Tw.js → vnode-BM3OHxAT.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-DXlf9sA3.js → zoom-D1fbFiHB.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agenda.js +320 -32
  151. package/src/commands/agent.js +105 -11
  152. package/src/commands/context.js +125 -14
  153. package/src/commands/doctor.js +85 -1
  154. package/src/commands/hook.js +181 -7
  155. package/src/commands/plugin.js +456 -1
  156. package/src/commands/review.js +363 -2
  157. package/src/commands/session.js +191 -0
  158. package/src/commands/team.js +19 -1
  159. package/src/data/changelog.json +10 -4
  160. package/src/gateways/ws/remote-session-protocol.js +39 -0
  161. package/src/gateways/ws/session-protocol.js +55 -2
  162. package/src/gateways/ws/ws-agent-handler.js +50 -0
  163. package/src/harness/jsonl-session-store.js +60 -0
  164. package/src/harness/worktree-isolator.js +140 -5
  165. package/src/lib/agent-schedule-store.js +318 -10
  166. package/src/lib/agent-team/team-worktree.js +37 -2
  167. package/src/lib/agent-tool-admission.js +125 -0
  168. package/src/lib/app-preview.js +360 -0
  169. package/src/lib/async-hook-supervisor.cjs +54 -0
  170. package/src/lib/capability-manifest.js +319 -0
  171. package/src/lib/capability-negotiation.js +23 -12
  172. package/src/lib/channels/channel-manager.js +54 -2
  173. package/src/lib/cli-context-engineering.js +9 -2
  174. package/src/lib/context-breakdown.js +116 -0
  175. package/src/lib/diagnostic-bundle.js +230 -0
  176. package/src/lib/doctor-checkup.js +275 -4
  177. package/src/lib/execution-location.js +286 -0
  178. package/src/lib/headless-manifest.js +13 -40
  179. package/src/lib/hook-event-log.cjs +193 -0
  180. package/src/lib/hook-stats-store.cjs +197 -0
  181. package/src/lib/idempotency.js +109 -0
  182. package/src/lib/interaction-adapter.js +34 -4
  183. package/src/lib/json-schema-output.js +46 -12
  184. package/src/lib/json-schema-validate.js +143 -3
  185. package/src/lib/jsonl-session-store.js +1 -0
  186. package/src/lib/lsp/__tests__/lsp-manager.test.js +58 -0
  187. package/src/lib/lsp/lsp-manager.js +56 -0
  188. package/src/lib/monitor-event.js +290 -0
  189. package/src/lib/operation-fingerprint.js +217 -0
  190. package/src/lib/plugin-runtime/bin.js +37 -0
  191. package/src/lib/plugin-runtime/capabilities.js +19 -0
  192. package/src/lib/plugin-runtime/capability-consent.js +203 -0
  193. package/src/lib/plugin-runtime/hooks.js +37 -0
  194. package/src/lib/plugin-runtime/lsp.js +38 -0
  195. package/src/lib/plugin-runtime/mcp.js +40 -0
  196. package/src/lib/plugin-runtime/monitors.js +37 -0
  197. package/src/lib/plugin-runtime/plugin-options.js +151 -0
  198. package/src/lib/plugin-runtime/policy.js +62 -0
  199. package/src/lib/plugin-runtime/scopes.js +20 -4
  200. package/src/lib/pr-automation-policy.js +224 -0
  201. package/src/lib/project-instructions.js +128 -0
  202. package/src/lib/remote-approval-bridge.js +66 -13
  203. package/src/lib/repl-goal.js +132 -0
  204. package/src/lib/repl-rewind.js +153 -0
  205. package/src/lib/review-comment-anchor.js +241 -0
  206. package/src/lib/review-pipeline.js +46 -7
  207. package/src/lib/runtime-checkup.js +221 -0
  208. package/src/lib/schedule-planner.js +17 -1
  209. package/src/lib/secret-scan.js +409 -0
  210. package/src/lib/session-branch.js +0 -0
  211. package/src/lib/settings-hook-events.cjs +13 -0
  212. package/src/lib/settings-loader.cjs +42 -0
  213. package/src/lib/side-effect-ledger-store.js +88 -0
  214. package/src/lib/side-effect-ledger.js +401 -0
  215. package/src/lib/skill-loader.js +32 -0
  216. package/src/lib/skill-path-scope.js +63 -0
  217. package/src/lib/sub-agent-context.js +110 -9
  218. package/src/lib/sub-agent-registry.js +19 -0
  219. package/src/lib/subagent-inheritance.js +112 -0
  220. package/src/lib/telemetry/span-recorder.js +17 -4
  221. package/src/lib/terminal-context.js +195 -0
  222. package/src/lib/turn-binding-store.js +119 -0
  223. package/src/lib/unattended-action-policy.js +182 -0
  224. package/src/lib/worktree-cleanup-safety.js +138 -0
  225. package/src/lib/worktree-isolator.js +8 -0
  226. package/src/lib/worktree-sparse.js +131 -0
  227. package/src/repl/agent-repl.js +280 -30
  228. package/src/repl/permission-tier.js +28 -0
  229. package/src/runtime/agent-core.js +565 -21
  230. package/src/runtime/coding-agent-contract-shared.cjs +60 -1
  231. package/src/runtime/diagnostics.js +60 -0
  232. package/src/runtime/headless-runner.js +393 -1
  233. package/src/runtime/headless-stream.js +60 -1
  234. package/src/assets/web-panel/assets/MobileProjects-D10VISps.js +0 -1
  235. package/src/assets/web-panel/assets/devWarning-DhiGrbsh.js +0 -1
  236. package/src/assets/web-panel/assets/index-B7a1TrU6.js +0 -1
  237. package/src/assets/web-panel/assets/index-C2B1KRja.js +0 -1
@@ -0,0 +1,409 @@
1
+ /**
2
+ * Recall-first secret scanner + a labeled corpus and recall / false-positive /
3
+ * latency benchmark (IDE gap §8.1 "凭据脱敏真实语料基准" of
4
+ * CLAUDE_CODE_IDE_INCREMENTAL_GAP_ANALYSIS_2026-07-13.md).
5
+ *
6
+ * [[ide-context-redaction.js]] `redactSecretsInText` is deliberately PRECISION-
7
+ * first: it guards editor context that rides into the model, where mangling
8
+ * everyday code teaches users to disable it. That posture is right there, but it
9
+ * is the wrong one for the EXPORT gate — a diagnostic bundle / artifact / trace
10
+ * that leaves the machine must fail toward NOT leaking, i.e. recall-first. §8.1
11
+ * asks to prove a redactor with a real corpus (provider tokens, JWT, Bearer,
12
+ * PEM, cloud creds, connection strings, cookies, `.env` variants) plus counter-
13
+ * examples (sample values, hashes, UUIDs, plain base64) and to report recall,
14
+ * false-positive rate, P95 latency and max input size — per surface.
15
+ *
16
+ * This module is that recall-first scanner (`scanSecrets` / `redactSecrets`),
17
+ * the labeled `SECRET_CORPUS`, and `runRedactionBenchmark` which measures ANY
18
+ * redactor against it. It does NOT touch the precision-first IDE redactor, so
19
+ * default IDE-context behavior is unchanged; this is the scanner the future
20
+ * "Secret Scan before export" gate (P1-9 诊断包) runs.
21
+ *
22
+ * Pure: no fs / RNG / clock. Latency is measured only when a `clock` is
23
+ * injected, keeping the module deterministic in tests.
24
+ */
25
+
26
+ import { isSecretEnvName } from "./credential-guard.js";
27
+
28
+ export const REDACTED = "[REDACTED]";
29
+
30
+ /** The export surfaces §8.1 wants measured independently. */
31
+ export const SURFACES = Object.freeze([
32
+ "source_context",
33
+ "transcript",
34
+ "trace",
35
+ "artifact",
36
+ "diagnostic_bundle",
37
+ ]);
38
+
39
+ /** The secret categories the scanner covers. */
40
+ export const SECRET_CATEGORIES = Object.freeze([
41
+ "provider_token",
42
+ "jwt",
43
+ "bearer",
44
+ "pem_private_key",
45
+ "connection_string",
46
+ "cookie",
47
+ "env_assignment",
48
+ ]);
49
+
50
+ // Each pattern is stored as a SOURCE string so `scanSecrets` compiles a fresh
51
+ // RegExp per call — a shared /g/ regex carries `lastIndex` state across calls
52
+ // and would skip matches. `replace` resets state itself, but scan/match do not.
53
+ const PATTERNS = [
54
+ {
55
+ category: "provider_token",
56
+ // OpenAI/Anthropic sk-, GitHub PAT/OAuth, GitLab PAT, Slack xox*, AWS keys.
57
+ source:
58
+ "\\b(?:sk-[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|glpat-[A-Za-z0-9_-]{16,}|xox[baprs]-[A-Za-z0-9-]{10,}|(?:AKIA|ASIA)[0-9A-Z]{16})\\b",
59
+ flags: "g",
60
+ replace: () => REDACTED,
61
+ },
62
+ {
63
+ category: "jwt",
64
+ // Three base64url segments; the first two begin `eyJ` (base64 of `{"`).
65
+ source:
66
+ "\\beyJ[A-Za-z0-9_-]{6,}\\.eyJ[A-Za-z0-9_-]{6,}\\.[A-Za-z0-9_-]{6,}\\b",
67
+ flags: "g",
68
+ replace: () => REDACTED,
69
+ },
70
+ {
71
+ category: "bearer",
72
+ source: "\\bBearer\\s+[A-Za-z0-9\\-._~+/=]{8,}",
73
+ flags: "g",
74
+ replace: () => `Bearer ${REDACTED}`,
75
+ },
76
+ {
77
+ category: "pem_private_key",
78
+ // The bytes ARE the secret; a truncated END marker still redacts to the end.
79
+ source:
80
+ "-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\\s\\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY-----|$)",
81
+ flags: "g",
82
+ replace: () => REDACTED,
83
+ },
84
+ {
85
+ category: "connection_string",
86
+ // scheme://user:PASSWORD@host — the embedded password is the secret. Only
87
+ // the credential form (with `:pass@`) matches, so `https://example.com` is
88
+ // untouched. Keep the scheme + user, swallow the password + host tail.
89
+ source:
90
+ "\\b((?:postgres|postgresql|mysql|mongodb(?:\\+srv)?|redis|rediss|amqp|amqps|https?|ftp|ssh):\\/\\/[^:\\s/@]+:)[^@\\s/]+@\\S+",
91
+ flags: "gi",
92
+ replace: (_m, prefix) => `${prefix}${REDACTED}`,
93
+ },
94
+ {
95
+ category: "cookie",
96
+ // A Cookie / Set-Cookie header value. Keep the header name, swallow the value.
97
+ source: "\\b(Set-Cookie|Cookie)(\\s*:\\s*)\\S[^\\n\\r]*",
98
+ flags: "gi",
99
+ replace: (_m, name, sep) => `${name}${sep}${REDACTED}`,
100
+ },
101
+ {
102
+ category: "env_assignment",
103
+ // `API_KEY = "value"` / `token: value`. The key must contain a boundary-
104
+ // delimited secret word (isSecretEnvName), the value a bare token-like run.
105
+ // The lookahead rejects `token = getToken()` (code, not a secret).
106
+ source:
107
+ "\\b([A-Za-z_][A-Za-z0-9_-]*)([\"']?\\s*[=:]\\s*)([\"']?)([A-Za-z0-9+/=_.\\-]{8,})\\3(?![A-Za-z0-9+/=_.\\-(])",
108
+ flags: "g",
109
+ replace: (m, key, sep, quote) =>
110
+ isSecretEnvName(key.replace(/-/g, "_"))
111
+ ? `${key}${sep}${quote}${REDACTED}${quote}`
112
+ : m, // key isn't secret-shaped → leave the assignment alone
113
+ // env_assignment only counts as a detection when the key gates true; a
114
+ // guard so scanSecrets doesn't report `count = 12345678` as a secret.
115
+ guard: (m, groups) => isSecretEnvName(String(groups[0]).replace(/-/g, "_")),
116
+ },
117
+ ];
118
+
119
+ /**
120
+ * Find every secret-shaped span in `text`. Recall-first: overlapping categories
121
+ * all report, so an assignment holding a JWT is caught by both.
122
+ *
123
+ * @param {string} text
124
+ * @returns {Array<{category:string, index:number, length:number, match:string}>}
125
+ */
126
+ export function scanSecrets(text) {
127
+ if (typeof text !== "string" || text.length === 0) return [];
128
+ const findings = [];
129
+ for (const p of PATTERNS) {
130
+ const re = new RegExp(p.source, p.flags);
131
+ for (const m of text.matchAll(re)) {
132
+ if (p.guard && !p.guard(m[0], m.slice(1))) continue;
133
+ findings.push({
134
+ category: p.category,
135
+ index: m.index,
136
+ length: m[0].length,
137
+ match: m[0],
138
+ });
139
+ }
140
+ }
141
+ return findings.sort((a, b) => a.index - b.index || a.length - b.length);
142
+ }
143
+
144
+ /**
145
+ * Redact every secret-shaped span, keeping the surviving structure a human /
146
+ * agent still needs (the `Bearer` word, a header/key name, a connection scheme)
147
+ * while swallowing the secret value.
148
+ *
149
+ * @param {string} text
150
+ * @returns {string}
151
+ */
152
+ export function redactSecrets(text) {
153
+ if (typeof text !== "string" || text.length === 0) return text;
154
+ let out = text;
155
+ for (const p of PATTERNS) {
156
+ const re = new RegExp(p.source, p.flags);
157
+ out = out.replace(re, p.replace);
158
+ }
159
+ return out;
160
+ }
161
+
162
+ /** Whether `text` contains any secret (the export gate's fail-closed check). */
163
+ export function containsSecret(text) {
164
+ return scanSecrets(text).length > 0;
165
+ }
166
+
167
+ // ── Labeled corpus ───────────────────────────────────────────────────────────
168
+ //
169
+ // Positives carry the exact `secret` substring that MUST disappear after
170
+ // redaction; negatives (sample/placeholder values, hashes, UUIDs, plain base64,
171
+ // ordinary code) MUST pass through byte-for-byte. Items are spread across the
172
+ // export surfaces so the benchmark can report per-surface.
173
+
174
+ const POSITIVES = [
175
+ {
176
+ id: "openai",
177
+ category: "provider_token",
178
+ surface: "transcript",
179
+ secret: "sk-abcd1234efgh5678ijkl",
180
+ text: 'client = OpenAI(api_key="sk-abcd1234efgh5678ijkl")',
181
+ },
182
+ {
183
+ id: "github_pat",
184
+ category: "provider_token",
185
+ surface: "artifact",
186
+ secret: "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
187
+ text: "git remote set-url origin https://ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@github.com/x/y",
188
+ },
189
+ {
190
+ id: "aws",
191
+ category: "provider_token",
192
+ surface: "diagnostic_bundle",
193
+ secret: "AKIAIOSFODNN7EXAMPLE",
194
+ text: "aws_access_key_id AKIAIOSFODNN7EXAMPLE",
195
+ },
196
+ {
197
+ id: "jwt",
198
+ category: "jwt",
199
+ surface: "trace",
200
+ secret:
201
+ "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U",
202
+ text: "Authorization header carried eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U",
203
+ },
204
+ {
205
+ id: "bearer",
206
+ category: "bearer",
207
+ surface: "source_context",
208
+ secret: "aG9sZDpteS1zZWNyZXQtdG9rZW4",
209
+ text: "curl -H 'Authorization: Bearer aG9sZDpteS1zZWNyZXQtdG9rZW4' https://api",
210
+ },
211
+ {
212
+ id: "pem",
213
+ category: "pem_private_key",
214
+ surface: "artifact",
215
+ secret:
216
+ "-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBAKj34Gkx...\n-----END RSA PRIVATE KEY-----",
217
+ text: "key file:\n-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBAKj34Gkx...\n-----END RSA PRIVATE KEY-----\n",
218
+ },
219
+ {
220
+ id: "postgres",
221
+ category: "connection_string",
222
+ surface: "diagnostic_bundle",
223
+ secret: "s3cr3tP@ss",
224
+ text: "DATABASE_URL=postgres://appuser:s3cr3tP@ss@db.internal:5432/prod",
225
+ },
226
+ {
227
+ id: "mongodb",
228
+ category: "connection_string",
229
+ surface: "trace",
230
+ secret: "hunter2hunter2",
231
+ text: "mongodb+srv://svc:hunter2hunter2@cluster0.mongodb.net/app",
232
+ },
233
+ {
234
+ id: "cookie",
235
+ category: "cookie",
236
+ surface: "transcript",
237
+ secret: "sessionid=9f8b7c6d5e4f3a2b1c0d",
238
+ text: "Set-Cookie: sessionid=9f8b7c6d5e4f3a2b1c0d; HttpOnly",
239
+ },
240
+ {
241
+ id: "env_password",
242
+ category: "env_assignment",
243
+ surface: "diagnostic_bundle",
244
+ secret: "Sup3rSecret_Value-123",
245
+ text: 'DB_PASSWORD="Sup3rSecret_Value-123"',
246
+ },
247
+ ];
248
+
249
+ const NEGATIVES = [
250
+ {
251
+ id: "uuid",
252
+ surface: "trace",
253
+ text: "requestId = 550e8400-e29b-41d4-a716-446655440000",
254
+ },
255
+ {
256
+ id: "git_sha",
257
+ surface: "transcript",
258
+ text: "commit beaa3f9ec7069258aad5e8587d32f779da5e5d20 landed",
259
+ },
260
+ {
261
+ id: "plain_base64",
262
+ surface: "artifact",
263
+ text: "payload blob aGVsbG8gd29ybGQgdGhpcyBpcyBub3QgYSBzZWNyZXQ=",
264
+ },
265
+ {
266
+ id: "prose",
267
+ surface: "source_context",
268
+ text: "The function returns the number of open editor tabs.",
269
+ },
270
+ {
271
+ id: "code_call",
272
+ surface: "source_context",
273
+ text: "const token = getToken(config)",
274
+ },
275
+ {
276
+ id: "nonsecret_assignment",
277
+ surface: "diagnostic_bundle",
278
+ text: "retryCount = 12345678",
279
+ },
280
+ {
281
+ id: "plain_url",
282
+ surface: "artifact",
283
+ text: "docs at https://example.com/guide/index.html",
284
+ },
285
+ {
286
+ id: "hex_digest",
287
+ surface: "diagnostic_bundle",
288
+ text: "sha256 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
289
+ },
290
+ ];
291
+
292
+ export const SECRET_CORPUS = Object.freeze({
293
+ positives: Object.freeze(POSITIVES.map((p) => Object.freeze(p))),
294
+ negatives: Object.freeze(NEGATIVES.map((n) => Object.freeze(n))),
295
+ });
296
+
297
+ function percentile(sortedAsc, p) {
298
+ if (sortedAsc.length === 0) return null;
299
+ const idx = Math.min(
300
+ sortedAsc.length - 1,
301
+ Math.max(0, Math.ceil((p / 100) * sortedAsc.length) - 1),
302
+ );
303
+ return sortedAsc[idx];
304
+ }
305
+
306
+ /**
307
+ * Measure a redactor against a labeled corpus: recall (positives whose secret
308
+ * disappeared), false-positive rate (negatives that were altered), max input
309
+ * size, and — only when a `clock` is injected — P95 redaction latency. Reports
310
+ * overall, per category (recall) and per surface (recall + FPR).
311
+ *
312
+ * @param {object} [corpus=SECRET_CORPUS]
313
+ * @param {object} [opts]
314
+ * @param {(text:string)=>string} [opts.redactor=redactSecrets]
315
+ * @param {()=>number} [opts.clock] ms clock; enables latency metrics
316
+ * @returns {object} the benchmark report
317
+ */
318
+ export function runRedactionBenchmark(corpus = SECRET_CORPUS, opts = {}) {
319
+ const redactor =
320
+ typeof opts.redactor === "function" ? opts.redactor : redactSecrets;
321
+ const clock = typeof opts.clock === "function" ? opts.clock : null;
322
+ const positives = Array.isArray(corpus?.positives) ? corpus.positives : [];
323
+ const negatives = Array.isArray(corpus?.negatives) ? corpus.negatives : [];
324
+
325
+ const latencies = [];
326
+ let maxInputBytes = 0;
327
+ const measure = (text) => {
328
+ maxInputBytes = Math.max(maxInputBytes, Buffer.byteLength(text, "utf8"));
329
+ if (!clock) return redactor(text);
330
+ const t0 = Number(clock());
331
+ const out = redactor(text);
332
+ const t1 = Number(clock());
333
+ if (Number.isFinite(t0) && Number.isFinite(t1)) latencies.push(t1 - t0);
334
+ return out;
335
+ };
336
+
337
+ const catRecall = {}; // category -> {detected, total}
338
+ const surface = {}; // surface -> {detected, posTotal, falsePos, negTotal}
339
+ const bumpSurface = (s) =>
340
+ (surface[s] = surface[s] || {
341
+ detected: 0,
342
+ posTotal: 0,
343
+ falsePos: 0,
344
+ negTotal: 0,
345
+ });
346
+
347
+ const missed = [];
348
+ let detected = 0;
349
+ for (const item of positives) {
350
+ const out = measure(item.text);
351
+ const ok = typeof item.secret === "string" && !out.includes(item.secret);
352
+ const cat = (catRecall[item.category] = catRecall[item.category] || {
353
+ detected: 0,
354
+ total: 0,
355
+ });
356
+ cat.total += 1;
357
+ const s = bumpSurface(item.surface);
358
+ s.posTotal += 1;
359
+ if (ok) {
360
+ detected += 1;
361
+ cat.detected += 1;
362
+ s.detected += 1;
363
+ } else {
364
+ missed.push(item.id);
365
+ }
366
+ }
367
+
368
+ const falsePositives = [];
369
+ let fp = 0;
370
+ for (const item of negatives) {
371
+ const out = measure(item.text);
372
+ const s = bumpSurface(item.surface);
373
+ s.negTotal += 1;
374
+ if (out !== item.text) {
375
+ fp += 1;
376
+ s.falsePos += 1;
377
+ falsePositives.push(item.id);
378
+ }
379
+ }
380
+
381
+ const byCategory = {};
382
+ for (const [c, v] of Object.entries(catRecall)) {
383
+ byCategory[c] = { recall: v.total ? v.detected / v.total : 1, ...v };
384
+ }
385
+ const bySurface = {};
386
+ for (const [sName, v] of Object.entries(surface)) {
387
+ bySurface[sName] = {
388
+ recall: v.posTotal ? v.detected / v.posTotal : 1,
389
+ falsePositiveRate: v.negTotal ? v.falsePos / v.negTotal : 0,
390
+ ...v,
391
+ };
392
+ }
393
+
394
+ const sorted = latencies.slice().sort((a, b) => a - b);
395
+ return {
396
+ overall: {
397
+ recall: positives.length ? detected / positives.length : 1,
398
+ falsePositiveRate: negatives.length ? fp / negatives.length : 0,
399
+ positives: positives.length,
400
+ negatives: negatives.length,
401
+ maxInputBytes,
402
+ p95LatencyMs: clock ? percentile(sorted, 95) : null,
403
+ },
404
+ missed,
405
+ falsePositives,
406
+ byCategory,
407
+ bySurface,
408
+ };
409
+ }
Binary file
@@ -19,12 +19,18 @@
19
19
  const { collectHooks } = require("./settings-hooks.cjs");
20
20
  const { runHooks } = require("./hook-runner.cjs");
21
21
  const { buildHookEnvelope } = require("./hook-event-bus.cjs");
22
+ const hookEventLog = require("./hook-event-log.cjs");
22
23
 
23
24
  /**
24
25
  * Stamp a unified-bus delivery id (event_id) onto a hook payload (P2 event bus).
25
26
  * Additive — a hook that ignores it is unaffected; a hook that reads it gets a
26
27
  * stable per-delivery id for correlation / `cc hook replay`. traceId/parentId
27
28
  * are threaded when the caller provides them (else null).
29
+ *
30
+ * When `CC_HOOK_EVENT_LOG` is enabled, the FULL envelope is best-effort recorded
31
+ * to the hash-chained hook event log so `cc hook replay <event-id>` has a real
32
+ * source. Recording is opt-in and never throws, so the default firing path is
33
+ * byte-for-byte unchanged.
28
34
  */
29
35
  function withDeliveryId(event, payload, { sessionId, traceId, parentId } = {}) {
30
36
  const env = buildHookEnvelope({
@@ -34,6 +40,13 @@ function withDeliveryId(event, payload, { sessionId, traceId, parentId } = {}) {
34
40
  traceId: traceId || null,
35
41
  parentId: parentId || null,
36
42
  });
43
+ if (hookEventLog.isHookEventLogEnabled()) {
44
+ try {
45
+ hookEventLog.appendHookEvent(env);
46
+ } catch {
47
+ /* best-effort — recording must not break the turn */
48
+ }
49
+ }
37
50
  return { ...payload, event_id: env.event_id };
38
51
  }
39
52
 
@@ -415,11 +415,53 @@ function readBooleanSetting(key, opts = {}) {
415
415
  return value;
416
416
  }
417
417
 
418
+ /**
419
+ * Read a string-array setting across the layered `.claude/settings.json` files
420
+ * plus managed settings. Unlike {@link readBooleanSetting} (last-layer wins),
421
+ * arrays UNION across layers (mirroring how `env`/`sandbox` accumulate in
422
+ * {@link loadSettingsConfig}) — a user-scope and a project-scope layer can each
423
+ * contribute entries. Non-array values and non-string members are ignored.
424
+ * Returns a de-duplicated array, or `undefined` when no layer sets it.
425
+ *
426
+ * @param {string} key settings key or dotted path (e.g. "instructionExcludes")
427
+ * @param {object} [opts]
428
+ * @param {string} [opts.cwd=process.cwd()]
429
+ * @param {string} [opts.settingsFile]
430
+ * @param {(msg:string)=>void} [opts.onWarn]
431
+ * @returns {string[]|undefined}
432
+ */
433
+ function readStringArraySetting(key, opts = {}) {
434
+ const cwd = opts.cwd || process.cwd();
435
+ const parts = String(key).split(".");
436
+ const collected = [];
437
+ let sawAny = false;
438
+ const absorb = (data) => {
439
+ let node = data;
440
+ for (const p of parts) {
441
+ node = node && typeof node === "object" ? node[p] : undefined;
442
+ }
443
+ if (Array.isArray(node)) {
444
+ sawAny = true;
445
+ for (const item of node) {
446
+ if (typeof item === "string" && item.trim()) collected.push(item);
447
+ }
448
+ }
449
+ };
450
+ for (const file of settingsPaths(cwd, opts.settingsFile)) {
451
+ absorb(readSettingsFile(file, { onWarn: opts.onWarn }));
452
+ }
453
+ const managed = loadManagedSettings(opts);
454
+ if (managed.settings) absorb(managed.settings);
455
+ if (!sawAny) return undefined;
456
+ return [...new Set(collected)];
457
+ }
458
+
418
459
  module.exports = {
419
460
  loadSettings,
420
461
  loadSettingsConfig,
421
462
  readSettingsFile,
422
463
  readBooleanSetting,
464
+ readStringArraySetting,
423
465
  settingsPaths,
424
466
  managedSettingsPath,
425
467
  loadManagedSettings,
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Persist the side-effect ledger ([[side-effect-ledger.js]]) as a tamper-evident
3
+ * chained session event so a worker that crashed mid `git push` / `file-write` /
4
+ * `package-install` can rebuild its ledger on recovery and reconcile WITHOUT
5
+ * repeating a side-effect it already issued.
6
+ *
7
+ * Same shape as [[turn-binding-store.js]]: each persist writes the FULL ledger
8
+ * snapshot (newest wins), all I/O is delegated to the canonical session store
9
+ * via `_deps` so the module stays pure-testable.
10
+ */
11
+
12
+ import {
13
+ appendEvent as storeAppendEvent,
14
+ readEvents as storeReadEvents,
15
+ } from "../harness/jsonl-session-store.js";
16
+ import {
17
+ SideEffectLedger,
18
+ reconcileSideEffects,
19
+ } from "./side-effect-ledger.js";
20
+
21
+ /** Event type carrying a full SideEffectLedger snapshot. */
22
+ export const SIDE_EFFECT_LEDGER_EVENT = "side_effect_ledger";
23
+
24
+ export const _deps = {
25
+ appendEvent: storeAppendEvent,
26
+ readEvents: storeReadEvents,
27
+ };
28
+
29
+ /**
30
+ * Append the current ledger as a chained event. Best-effort — a persistence
31
+ * failure must not crash the operation it is guarding.
32
+ *
33
+ * @param {string} sessionId
34
+ * @param {SideEffectLedger|object} ledger
35
+ * @returns {boolean}
36
+ */
37
+ export function persistSideEffectLedger(sessionId, ledger) {
38
+ if (!sessionId || !ledger) return false;
39
+ const snapshot =
40
+ ledger instanceof SideEffectLedger ? ledger.toJSON() : ledger;
41
+ if (!snapshot || !Array.isArray(snapshot.ops)) return false;
42
+ try {
43
+ _deps.appendEvent(sessionId, SIDE_EFFECT_LEDGER_EVENT, snapshot);
44
+ return true;
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Rebuild the ledger from the LATEST persisted snapshot, or an empty ledger when
52
+ * the session never persisted one (or on read error).
53
+ *
54
+ * @param {string} sessionId
55
+ * @param {{clock?:Function}} [opts]
56
+ * @returns {SideEffectLedger}
57
+ */
58
+ export function loadSideEffectLedger(sessionId, opts = {}) {
59
+ let events = [];
60
+ try {
61
+ events = _deps.readEvents(sessionId) || [];
62
+ } catch {
63
+ return new SideEffectLedger(opts);
64
+ }
65
+ for (let i = events.length - 1; i >= 0; i--) {
66
+ const e = events[i];
67
+ if (
68
+ e &&
69
+ e.type === SIDE_EFFECT_LEDGER_EVENT &&
70
+ e.data &&
71
+ Array.isArray(e.data.ops)
72
+ ) {
73
+ return SideEffectLedger.fromJSON(e.data, opts);
74
+ }
75
+ }
76
+ return new SideEffectLedger(opts);
77
+ }
78
+
79
+ /**
80
+ * Load + reconcile in one step: the recovery entry point. Returns the plan a
81
+ * restarting worker uses to decide, per op, redo / inspect / skip.
82
+ *
83
+ * @param {string} sessionId
84
+ * @returns {{plans:Array, redo:string[], inspect:string[], skip:string[]}}
85
+ */
86
+ export function reconcileSessionSideEffects(sessionId) {
87
+ return reconcileSideEffects(loadSideEffectLedger(sessionId));
88
+ }