chainlesschain 0.162.162 → 0.162.163

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 (192) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-ClI2ALgv.js → AIOps-BQ0kgfA1.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BDjRcJaK.js → ActionButton-Dr1MDANe.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DikSRsaT.js → Analytics-CZ5vsaXW.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-9mLhMFmz.js → AppLayout-BIBga881.js} +3 -3
  6. package/src/assets/web-panel/assets/{Artifacts-BSr88BrH.js → Artifacts-vpXHRU3u.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-CdvyimOF.js → Audit-B8exuoub.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-D3_hCedh.js → BackgroundAgents-923evRRN.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-Cyvv9RHf.js → Backup-sr_sswlQ.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-BZM14kt8.js → BaseInput-CnW-4XKT.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-DrQSokVz.js → Chat-FZCsTZIL.js} +6 -6
  12. package/src/assets/web-panel/assets/ChatBubbleRenderer-DDPd9qT0.js +1 -0
  13. package/src/assets/web-panel/assets/{Checkbox-bFLYeWMV.js → Checkbox-DFrxB59H.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-7mN7xJXT.js → Codegen-_b6WEel8.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-DAB5ZWm7.js → Col-CkbnnZ-K.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-B_1TymaK.js → Community-BxM_82zW.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-Bq1uSWLJ.js → Compact-DYinP45R.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-CyiE1jEe.js → Compliance-DyXp3sSP.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-Cpk0D7vm.js → Cowork-BEr2DhW7.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-d_Lv3rdB.js → Cron-B-Vwo1hN.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DMqhHjMR.js → Crosschain-DbLxKddd.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-Br-oPuBw.js → DID-BsQ_LFw9.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-CfLW_RaP.js → Dashboard-lVlXQHu5.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-DZnTOExV.js → Dropdown-Beo_foO7.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-DHIUHDv1.js → EmailListRenderer-DszHIXED.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bw3t3myu.js → FamilyGuardDashboard-DcAwoh81.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-D9cpQPAY.js → Federation-CuJLa2eb.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-Jdczr9Mr.js → FormItemContext-Cxd1lxhc.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-BT8ZMpC5.js → GenericCardRenderer-BBvq5Jsw.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-DjARip2b.js → Git-WqPKetx6.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-BgWbSRcj.js → Governance-CWYtY3ke.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-CfouJciM.js → Inference-BnX1iiNV.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-50l7mrAr.js → KnowledgeGraph-BJuM8GN5.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-rq50H84B.js → Logs-DqLMNntm.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-DUPIEdQH.js → MarkdownRenderer-BqYwEYou.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-BYgPDmHU.js → Marketplace-_vGfmhNl.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-5DVgcFdx.js → McpTools-BeJDOkDj.js} +3 -3
  38. package/src/assets/web-panel/assets/{Memory-B70LPk7f.js → Memory-Da0EEZDO.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-De8ssA5Y.js → MobileBridge-DSuzcCCy.js} +1 -1
  40. package/src/assets/web-panel/assets/MobileProjects-D0RAk4e8.js +1 -0
  41. package/src/assets/web-panel/assets/{Mtc-RJsOTsIA.js → Mtc-BE9YgdA0.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-Crh-6BEK.js → MtcAudit-fC2BpO5S.js} +5 -5
  43. package/src/assets/web-panel/assets/{Multisig-wuoR5l3x.js → Multisig-D24Xxqw3.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-U2Mji4fZ.js → NLProgramming-Bs3oGvS8.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-CdTZAyDH.js → Notes-BFjmAA-3.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-C-FucWE7.js → NotificationSettings-BX_9Bhv0.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-DCYFTeaI.js → OrderTableRenderer-wjkZwJXI.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-an3OF85L.js → Organization-DBcdsVEP.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-C-1vmYFe.js → Overflow-DDyaF9vC.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-Cwh47xKg.js → P2P-BB78L4he.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-Dm38GT0k.js → PdhVaultBrowser-DqTiZFum.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-DS2Y8awD.js → Permissions-INbnWv4j.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-BQNziA0I.js → PersonalDataHub-BST-VmTq.js} +3 -3
  54. package/src/assets/web-panel/assets/{Pipeline-DF85L1ue.js → Pipeline-CE7_tS2k.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-BuLornF-.js → Privacy-hK5L8dOB.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-C3Y31IwX.js → ProjectInit-BvkkUoDn.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-BCPiI8aD.js → ProjectSettings-u7_EUtkm.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-BZ6s4k6g.js → Projects-5Fmt_8Nw.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-D3Wp022X.js → Providers-B9eanarR.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-CUJfQkH4.js → QrScannerModal-C0dX5N2f.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-UAc0EO8l.js → QuickAsk-CKTpG-PZ.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-BaxKupcs.js → Recommend-Px_w_XQM.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-C75NNnXZ.js → RemoteSession-Di0fnYkM.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-BVXynZAk.js → Reputation-vesnWjXt.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-C5MRZ0bi.js → Row-exbY6LkT.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-DAKkEYka.js → RssFeed-B3O3x70t.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-DvWv9YOA.js → Search-uRA0DhNz.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-B5peafW1.js → Security-CViqdH6K.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-UQmvPEaG.js → Services-CkXS97WN.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-C3bhWpuF.js → Skeleton-DxW5WYnE.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-DfkKf9_H.js → Skills-CLxmGFse.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-DqMSjlBM.js → Sla-Cr5fziXq.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-C-WbxWUX.js → SpeechSettings-H0JSb3cp.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-CYuHZ4N2.js → SyncSettings-DMYWMae7.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-CyeAyB9d.js → Tasks-CbYyDdF4.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-BTCeQnpq.js → Templates-Cpc8JjiK.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-BifQuA9Y.js → Tenant-CRh77Jda.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-Bx_9Bocf.js → Terminal-4D8tNBO5.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-BX0RFCOm.js → TimelineRenderer-D41jzzbG.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-CGHrCmUS.js → Tokens-C_jWEy6b.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-DlqaY4SO.js → Trigger-Dk1ZUhsy.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-DfOmj-ay.js → Trust-CLVy4l7m.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-B72jH5fE.js → UkeySign-C-FeqFDj.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-BT-63fqv.js → VideoEditing-CqVZzyMm.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-Dsul3sHr.js → Wallet-DX9BW9pu.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-Izkwwdgy.js → WebAuthn-7hQDpWw_.js} +2 -2
  87. package/src/assets/web-panel/assets/{WorkflowEditor-D3FHSiH1.js → WorkflowEditor-6XqUAO0j.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-CmVz5z3U.js → chat-5R_vkaJG.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-CuLgyJBp.js → colors-huUPZdC-.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-Ml1YkXpw.js → compact-item-CfJlgisE.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-BvjJ48bs.js → createContext-jO8heofr.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-DeIrCfH-.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DVC0157C.js → hasIn-DY8Vxqx7.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-DJyN28Et.js → index-A48PtXw9.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CudmwNBV.js → index-B300RPnT.js} +3 -3
  96. package/src/assets/web-panel/assets/{index-BbepULSc.js → index-B74OXFxC.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DYXHFr4r.js → index-BAON7TzU.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BzG-x9p4.js → index-BCqxyvqp.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-wvb_FtaV.js → index-BHE8OIls.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-QXzlT9KD.js → index-BQS-uPOK.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DukDVXCv.js → index-BRXOP09X.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CFYVQMvu.js → index-BYLBPpt8.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BydjdZnl.js → index-BfCx4pFT.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CmFNvNLZ.js → index-BliaT02i.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-KMqhcF7l.js → index-BmqGerpC.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Ck_52dz_.js → index-BsBtRWbA.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-C229ABfy.js → index-BtEXSoPL.js} +1 -1
  108. package/src/assets/web-panel/assets/index-BxUKMRpP.js +1 -0
  109. package/src/assets/web-panel/assets/{index-C_H_S-DY.js → index-Bxax9vQ6.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-B0S8NzO6.js → index-C2iVSFpy.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BjHBpRYZ.js → index-CA6sO5VD.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Bzr0rB5T.js → index-CA90TZxG.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DDGaOWBV.js → index-CLhBh-qE.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Dt5KMjwJ.js → index-CRElRhyS.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BbPtzB8M.js → index-Cbe9G0V4.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-C_vRgTl9.js → index-ChvHubX3.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BpFJHiTv.js → index-Cis_Ip7H.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-KjgKdZe-.js → index-Cjxbu68P.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-vG4XSJ1d.js → index-CzKmteNS.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BUt8kTIB.js → index-D0pet5Uy.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BfuKP4qe.js → index-D1VLuww0.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-4SBta3d7.js → index-DXBcvUnf.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DC_OI9H2.js → index-Db1cLWir.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DuGszaWY.js → index-DoI2sZm7.js} +1 -1
  125. package/src/assets/web-panel/assets/index-DscU9iPI.js +1 -0
  126. package/src/assets/web-panel/assets/{index-4wcgvPoF.js → index-KRHroryI.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-D3lD5qap.js → index-d1xkbtNz.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-B_EvG_GW.js → index-dKx57io7.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CXkJH-lK.js → index-j3O2EEJe.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BiC6dQZG.js → index-p85YaaKw.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Cw0Rjy87.js → index-v3pTaEQm.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-C_XuRgGn.js → index-vXs3B8GJ.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-BDgd2YZ8.js → initDefaultProps-MCB7-raZ.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-ErtrXdYe.js → motion-UWDF9swV.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-_GPPNCgs.js → move-CsTmgXZU.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-DRLL-8V2.js → mtc-parser-fQy6KGLg.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-p86sl023.js → omit-woPnEjd1.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-BTu7cMLH.js → pickAttrs-Bs2dZ1gf.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-BEKz6Isr.js → placementArrow-C7EVgPkg.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-Ck42c9MP.js → responsiveObserve-BH2kYCTN.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-1CEvuPsB.js → slide-BFBP2V_e.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-BDfdx1GJ.js → statusUtils-xdDxDYse.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-B4K46m5w.js → styleChecker-aAWgPPQf.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-DoIVpJNq.js → useFlexGapSupport-DkGSAWoO.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-DdAWi-ln.js → useFs-BrOih40N.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-iUu6k8dx.js → usePersonalDataHub-eIYrgMqO.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-DZz4R6Tw.js → vnode-B8lutNSE.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-DXlf9sA3.js → zoom-CffNre7V.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agenda.js +254 -23
  151. package/src/commands/agent.js +105 -11
  152. package/src/commands/context.js +125 -14
  153. package/src/commands/hook.js +181 -7
  154. package/src/commands/plugin.js +239 -1
  155. package/src/commands/review.js +347 -2
  156. package/src/data/changelog.json +9 -4
  157. package/src/gateways/ws/remote-session-protocol.js +39 -0
  158. package/src/gateways/ws/session-protocol.js +5 -2
  159. package/src/lib/agent-schedule-store.js +289 -10
  160. package/src/lib/channels/channel-manager.js +54 -2
  161. package/src/lib/cli-context-engineering.js +9 -2
  162. package/src/lib/context-breakdown.js +116 -0
  163. package/src/lib/doctor-checkup.js +52 -0
  164. package/src/lib/hook-event-log.cjs +193 -0
  165. package/src/lib/interaction-adapter.js +34 -4
  166. package/src/lib/json-schema-output.js +46 -12
  167. package/src/lib/json-schema-validate.js +143 -3
  168. package/src/lib/plugin-runtime/capability-consent.js +175 -0
  169. package/src/lib/plugin-runtime/policy.js +62 -0
  170. package/src/lib/plugin-runtime/scopes.js +20 -4
  171. package/src/lib/project-instructions.js +128 -0
  172. package/src/lib/repl-goal.js +132 -0
  173. package/src/lib/repl-rewind.js +52 -0
  174. package/src/lib/schedule-planner.js +17 -1
  175. package/src/lib/settings-hook-events.cjs +13 -0
  176. package/src/lib/settings-loader.cjs +42 -0
  177. package/src/lib/side-effect-ledger-store.js +88 -0
  178. package/src/lib/side-effect-ledger.js +296 -0
  179. package/src/lib/sub-agent-context.js +74 -5
  180. package/src/lib/subagent-inheritance.js +112 -0
  181. package/src/lib/turn-binding-store.js +119 -0
  182. package/src/repl/agent-repl.js +136 -0
  183. package/src/repl/permission-tier.js +28 -0
  184. package/src/runtime/agent-core.js +376 -10
  185. package/src/runtime/coding-agent-contract-shared.cjs +60 -1
  186. package/src/runtime/headless-runner.js +267 -0
  187. package/src/runtime/headless-stream.js +60 -1
  188. package/src/assets/web-panel/assets/ChatBubbleRenderer-HwJR-C6B.js +0 -1
  189. package/src/assets/web-panel/assets/MobileProjects-D10VISps.js +0 -1
  190. package/src/assets/web-panel/assets/devWarning-DhiGrbsh.js +0 -1
  191. package/src/assets/web-panel/assets/index-B7a1TrU6.js +0 -1
  192. package/src/assets/web-panel/assets/index-C2B1KRja.js +0 -1
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * hook-event-log — a persistent, hash-chained, replay-safe log of the hook
5
+ * delivery envelopes produced by the unified event bus ([[hook-event-bus.cjs]]).
6
+ *
7
+ * The event bus already stamps every settings-hook delivery with a stable
8
+ * `event_id` (see [[settings-hook-events.cjs]] `withDeliveryId`), but until now
9
+ * the full envelope was discarded — so there was nothing to look up when a user
10
+ * wants `cc hook replay <event-id>`. This store persists each envelope to a
11
+ * tamper-evident JSONL log so the replay command has a real source.
12
+ *
13
+ * Design:
14
+ * - OPT-IN: recording only happens when `CC_HOOK_EVENT_LOG` is enabled, so the
15
+ * default hook-firing path is byte-for-byte unchanged (no new IO per turn).
16
+ * - Hash chain: each line is `{envelope, prevHash, hash}` where
17
+ * `hash = sha256(prevHash + stableStringify(envelope))`. Editing / deleting /
18
+ * reordering any recorded line breaks the chain (mirrors the session
19
+ * transcript integrity design), which `verifyHookEventChain` detects.
20
+ * - Size guard: the log rotates to `<file>.1` once it exceeds MAX_LOG_BYTES so
21
+ * an always-on session can't grow it without bound.
22
+ * - Pure over injected IO (`_deps.fs` / `_deps.os`) so it is fully unit-testable
23
+ * without touching the real home directory.
24
+ */
25
+
26
+ const fs = require("fs");
27
+ const os = require("os");
28
+ const path = require("path");
29
+ const crypto = require("crypto");
30
+ const { stableStringify } = require("./hook-event-bus.cjs");
31
+
32
+ const _deps = { fs, os };
33
+
34
+ /** Cap the always-on log at 5 MiB, then rotate to `<file>.1`. */
35
+ const MAX_LOG_BYTES = 5 * 1024 * 1024;
36
+
37
+ /** Default log location: `~/.chainlesschain/hook-events.jsonl`. */
38
+ function hookEventLogPath(homedir) {
39
+ const home = homedir || _deps.os.homedir();
40
+ return path.join(home, ".chainlesschain", "hook-events.jsonl");
41
+ }
42
+
43
+ /** Recording is opt-in — `CC_HOOK_EVENT_LOG=1` (or `true`) turns it on. */
44
+ function isHookEventLogEnabled(env = process.env) {
45
+ const v = String(env.CC_HOOK_EVENT_LOG || "").toLowerCase();
46
+ return v === "1" || v === "true" || v === "yes" || v === "on";
47
+ }
48
+
49
+ /** Hash one envelope against the previous chain hash (deterministic). */
50
+ function computeChainHash(prevHash, envelope) {
51
+ return crypto
52
+ .createHash("sha256")
53
+ .update(`${prevHash || "genesis"}\n${stableStringify(envelope)}`, "utf-8")
54
+ .digest("hex");
55
+ }
56
+
57
+ /** The hash the NEXT record must chain from (last parseable line's `hash`). */
58
+ function latestChainHash(text) {
59
+ const lines = String(text || "").split("\n");
60
+ for (let i = lines.length - 1; i >= 0; i--) {
61
+ if (!lines[i].trim()) continue;
62
+ try {
63
+ const rec = JSON.parse(lines[i]);
64
+ return typeof rec?.hash === "string" ? rec.hash : null;
65
+ } catch {
66
+ // half-written tail line — chain from the previous intact record
67
+ continue;
68
+ }
69
+ }
70
+ return null;
71
+ }
72
+
73
+ /**
74
+ * Append a hook envelope to the chained log. Best-effort: never throws — a
75
+ * failure to record must not break the turn that fired the hook.
76
+ *
77
+ * @param {object} envelope a full envelope from buildHookEnvelope
78
+ * @param {object} [opts] { filePath, homedir }
79
+ * @returns {{ok:boolean, event_id?:string, hash?:string, reason?:string}}
80
+ */
81
+ function appendHookEvent(envelope, { filePath, homedir } = {}) {
82
+ if (!envelope || typeof envelope !== "object" || !envelope.event_id) {
83
+ return { ok: false, reason: "invalid envelope (no event_id)" };
84
+ }
85
+ const f = _deps.fs;
86
+ const fp = filePath || hookEventLogPath(homedir);
87
+ try {
88
+ const dir = path.dirname(fp);
89
+ if (!f.existsSync(dir)) f.mkdirSync(dir, { recursive: true });
90
+ let prevHash = null;
91
+ if (f.existsSync(fp)) {
92
+ let rotated = false;
93
+ try {
94
+ if (f.statSync(fp).size > MAX_LOG_BYTES) {
95
+ f.renameSync(fp, `${fp}.1`);
96
+ rotated = true;
97
+ }
98
+ } catch {
99
+ /* stat/rename best-effort */
100
+ }
101
+ if (!rotated) prevHash = latestChainHash(f.readFileSync(fp, "utf-8"));
102
+ }
103
+ const hash = computeChainHash(prevHash, envelope);
104
+ const line = `${JSON.stringify({ envelope, prevHash, hash })}\n`;
105
+ f.appendFileSync(fp, line, "utf-8");
106
+ return { ok: true, event_id: envelope.event_id, hash };
107
+ } catch (e) {
108
+ return { ok: false, reason: e.message };
109
+ }
110
+ }
111
+
112
+ /** Read every recorded record (skips malformed / half-written lines). */
113
+ function readHookEventRecords({ filePath, homedir } = {}) {
114
+ const f = _deps.fs;
115
+ const fp = filePath || hookEventLogPath(homedir);
116
+ if (!f.existsSync(fp)) return [];
117
+ const out = [];
118
+ for (const line of f.readFileSync(fp, "utf-8").split("\n")) {
119
+ if (!line.trim()) continue;
120
+ try {
121
+ out.push(JSON.parse(line));
122
+ } catch {
123
+ /* skip malformed / partial line */
124
+ }
125
+ }
126
+ return out;
127
+ }
128
+
129
+ /**
130
+ * Look up the envelope for a delivery id. The LATEST matching record wins (an
131
+ * event_id can recur across processes; the most recent delivery is what a user
132
+ * replaying "the last one" means).
133
+ */
134
+ function findHookEvent(eventId, { filePath, homedir } = {}) {
135
+ if (!eventId) return null;
136
+ const records = readHookEventRecords({ filePath, homedir });
137
+ for (let i = records.length - 1; i >= 0; i--) {
138
+ if (records[i]?.envelope?.event_id === eventId) return records[i].envelope;
139
+ }
140
+ return null;
141
+ }
142
+
143
+ /** The most recent `limit` envelopes (newest last), for `cc hook events-log`. */
144
+ function listHookEvents({ limit = 20, filePath, homedir } = {}) {
145
+ const records = readHookEventRecords({ filePath, homedir });
146
+ const n = Math.max(0, Number(limit) || 0);
147
+ return records.slice(n ? -n : 0).map((r) => r.envelope);
148
+ }
149
+
150
+ /**
151
+ * Recompute the chain and report the first break, if any.
152
+ * @returns {{ok:boolean, length:number, brokenAt:number, reason?:string}}
153
+ */
154
+ function verifyHookEventChain({ filePath, homedir } = {}) {
155
+ const records = readHookEventRecords({ filePath, homedir });
156
+ let prev = null;
157
+ for (let i = 0; i < records.length; i++) {
158
+ const { envelope, prevHash, hash } = records[i] || {};
159
+ if ((prevHash ?? null) !== prev) {
160
+ return {
161
+ ok: false,
162
+ length: records.length,
163
+ brokenAt: i,
164
+ reason: "prevHash does not match the previous record's hash",
165
+ };
166
+ }
167
+ const expect = computeChainHash(prev, envelope);
168
+ if (expect !== hash) {
169
+ return {
170
+ ok: false,
171
+ length: records.length,
172
+ brokenAt: i,
173
+ reason: "record hash does not match its envelope (tampered)",
174
+ };
175
+ }
176
+ prev = hash;
177
+ }
178
+ return { ok: true, length: records.length, brokenAt: -1 };
179
+ }
180
+
181
+ module.exports = {
182
+ MAX_LOG_BYTES,
183
+ hookEventLogPath,
184
+ isHookEventLogEnabled,
185
+ computeChainHash,
186
+ latestChainHash,
187
+ appendHookEvent,
188
+ readHookEventRecords,
189
+ findHookEvent,
190
+ listHookEvents,
191
+ verifyHookEventChain,
192
+ _deps,
193
+ };
@@ -14,6 +14,7 @@ import {
14
14
  LEGACY_TO_UNIFIED_TYPE,
15
15
  } from "../runtime/runtime-events.js";
16
16
  import { createAbortError } from "./abort-utils.js";
17
+ import { verifyApprovalBinding } from "./agent-authority.js";
17
18
  import { createEnvelope } from "@chainlesschain/session-core";
18
19
 
19
20
  // Phase 5: parallel service-envelope emission. Map WS agent-handler event
@@ -167,12 +168,21 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
167
168
  reject,
168
169
  timeoutId,
169
170
  kind: options.kind || null,
171
+ // Approval binding (authority §"权限来源"): when a permission gate raises
172
+ // this request WITH a binding (tool_call_id + args + policy digest), an
173
+ // *approve* answer must echo the SAME binding or it is a stale / mis-
174
+ // routed / argument-tampered verdict and is rejected in _resolvePending.
175
+ // null (the default for plain questions) → no binding check, unchanged.
176
+ binding: options.binding || null,
170
177
  });
171
178
 
172
179
  this._sendWs({
173
180
  ...message,
174
181
  sessionId: this.sessionId,
175
182
  requestId,
183
+ // Ride the binding out so the remote UI/bridge can echo it back on the
184
+ // answer (only present when the caller supplied one).
185
+ ...(options.binding ? { binding: options.binding } : {}),
176
186
  });
177
187
  });
178
188
  }
@@ -210,8 +220,8 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
210
220
  * Called by ws-server when a session-answer message arrives.
211
221
  * Resolves the corresponding pending promise.
212
222
  */
213
- resolveAnswer(requestId, answer) {
214
- this._resolvePending(requestId, answer, "question");
223
+ resolveAnswer(requestId, answer, binding = null) {
224
+ this._resolvePending(requestId, answer, "question", binding);
215
225
  }
216
226
 
217
227
  async requestHostTool(toolName, args = {}, extra = {}) {
@@ -321,7 +331,12 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
321
331
  }
322
332
  }
323
333
 
324
- _resolvePending(requestId, payload, expectedKind = null) {
334
+ _resolvePending(
335
+ requestId,
336
+ payload,
337
+ expectedKind = null,
338
+ incomingBinding = null,
339
+ ) {
325
340
  const pending = this._pending.get(requestId);
326
341
  if (!pending) {
327
342
  return;
@@ -337,11 +352,26 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
337
352
  return;
338
353
  }
339
354
 
355
+ // Approval-binding guard (authority §"权限来源"): if this request was raised
356
+ // with a binding AND the answer echoes a binding that does NOT match, the
357
+ // verdict is for a different / argument-tampered call — settle as a DENY
358
+ // (fail closed) instead of the approve it claims to be, mirroring the local
359
+ // headless approval gate. Backward-compatible: no request binding, or an
360
+ // answer with no echoed binding, resolves exactly as before.
361
+ let effective = payload;
362
+ if (
363
+ pending.binding &&
364
+ incomingBinding &&
365
+ !verifyApprovalBinding(pending.binding, incomingBinding)
366
+ ) {
367
+ effective = false;
368
+ }
369
+
340
370
  this._pending.delete(requestId);
341
371
  if (pending.timeoutId) {
342
372
  clearTimeout(pending.timeoutId);
343
373
  }
344
- pending.resolve(payload);
374
+ pending.resolve(effective);
345
375
  }
346
376
  }
347
377
 
@@ -7,13 +7,17 @@
7
7
  * `deps.writeOut` capture seam — the runner itself is untouched: each attempt
8
8
  * runs with output captured, the validated JSON is the only thing printed.
9
9
  *
10
- * Validator subset (enough for tool/script contracts, not full draft-2020):
11
- * type (object/array/string/number/integer/boolean/null), properties,
12
- * required, items, enum, const, additionalProperties:false. Zero deps.
10
+ * Constrained validation delegates to the richer Draft-2020-12-subset validator
11
+ * in [[json-schema-validate.js]] (type, properties, required, items/prefixItems,
12
+ * enum/const, additionalProperties, min/max, pattern, format, allOf/anyOf/oneOf/
13
+ * not, if/then/else, local $ref) so retries get precise coded/pointered errors —
14
+ * the model is told exactly WHICH JSON Pointer failed and WHY. The legacy
15
+ * `validateAgainstSchema` (loose `$.a.b` strings) stays exported for callers.
13
16
  */
14
17
 
15
18
  import fsDefault from "fs";
16
19
  import {
20
+ validate,
17
21
  validateSchema,
18
22
  buildStructuredResultEvent,
19
23
  computeSchemaDigest,
@@ -96,6 +100,20 @@ export function validateAgainstSchema(value, schema, path = "$") {
96
100
  return errors;
97
101
  }
98
102
 
103
+ /**
104
+ * Render the richer validator's coded/pointered error objects as human-readable
105
+ * `<JSON Pointer>: <message>` strings for the corrective retry prompt and the
106
+ * final failure report. The empty root pointer is shown as `(root)`.
107
+ *
108
+ * @param {Array<{instancePath?:string, message:string}>} errors
109
+ * @returns {string[]}
110
+ */
111
+ export function formatSchemaErrors(errors) {
112
+ return (errors || []).map(
113
+ (e) => `${e.instancePath || "(root)"}: ${e.message}`,
114
+ );
115
+ }
116
+
99
117
  /**
100
118
  * Return the first *balanced* JSON object/array substring in `text`, honoring
101
119
  * string literals so braces inside strings don't throw off the depth count.
@@ -203,18 +221,29 @@ export function loadSchemaFile(fs, schemaFile) {
203
221
  "No schema provided: pass --json-schema <file> or a schema object",
204
222
  );
205
223
  }
224
+ // Inline JSON support: a value that starts with `{` is treated as a literal
225
+ // schema object, never a path (a filesystem path can't start with `{`). This
226
+ // lets `--json-schema '{"type":"object",...}'` work without a temp file.
227
+ const trimmed = String(schemaFile).trim();
228
+ const isInline = trimmed.startsWith("{");
206
229
  let raw;
207
- try {
208
- raw = fs.readFileSync(schemaFile, "utf-8");
209
- } catch (e) {
210
- throw new Error(`Cannot read schema file "${schemaFile}": ${e.message}`);
230
+ if (isInline) {
231
+ raw = trimmed;
232
+ } else {
233
+ try {
234
+ raw = fs.readFileSync(schemaFile, "utf-8");
235
+ } catch (e) {
236
+ throw new Error(`Cannot read schema file "${schemaFile}": ${e.message}`);
237
+ }
211
238
  }
212
239
  let parsed;
213
240
  try {
214
241
  parsed = JSON.parse(raw);
215
242
  } catch (e) {
216
243
  throw new Error(
217
- `Invalid JSON in schema file "${schemaFile}": ${e.message}`,
244
+ isInline
245
+ ? `Invalid inline JSON schema: ${e.message}`
246
+ : `Invalid JSON in schema file "${schemaFile}": ${e.message}`,
218
247
  );
219
248
  }
220
249
  // Startup schema meta-validation (P2): reject a malformed schema up front
@@ -226,7 +255,9 @@ export function loadSchemaFile(fs, schemaFile) {
226
255
  .slice(0, 5)
227
256
  .map((e) => ` - ${e.schemaPath || "/"}: ${e.message}`)
228
257
  .join("\n");
229
- throw new Error(`Invalid JSON Schema in "${schemaFile}":\n${detail}`);
258
+ throw new Error(
259
+ `Invalid JSON Schema in ${isInline ? "inline schema" : `"${schemaFile}"`}:\n${detail}`,
260
+ );
230
261
  }
231
262
  return parsed;
232
263
  }
@@ -275,12 +306,15 @@ export async function runJsonSchemaConstrained(cfg = {}) {
275
306
  lastRaw = raw;
276
307
  const parsed = extractJsonPayload(raw);
277
308
  if (parsed.ok) {
278
- const errors = validateAgainstSchema(parsed.value, schema);
279
- if (errors.length === 0) {
309
+ // Richer validator: enforces the full supported subset (format assertions,
310
+ // if/then/else, min/max, pattern, combinators, …) and yields coded/pointered
311
+ // errors so the retry prompt tells the model exactly which pointer failed.
312
+ const { valid, errors } = validate(parsed.value, schema);
313
+ if (valid) {
280
314
  writeOut(`${JSON.stringify(parsed.value, null, 2)}\n`);
281
315
  return 0;
282
316
  }
283
- lastErrors = errors;
317
+ lastErrors = formatSchemaErrors(errors);
284
318
  } else {
285
319
  lastErrors = [parsed.error];
286
320
  }
@@ -22,9 +22,11 @@
22
22
  * Supported keywords: type (+ integer), enum, const, properties, required,
23
23
  * additionalProperties (bool|schema), patternProperties-free, items,
24
24
  * prefixItems, minItems/maxItems, uniqueItems, minLength/maxLength, pattern,
25
- * minimum/maximum/exclusiveMinimum/exclusiveMaximum, multipleOf,
26
- * minProperties/maxProperties, allOf/anyOf/oneOf/not, and local `$ref`
27
- * (`#/$defs/…`, `#/definitions/…`, or any in-document JSON Pointer).
25
+ * format (date-time/date/time/email/uri/uuid/ipv4/ipv6/hostname — asserted;
26
+ * unknown formats pass through as annotations), minimum/maximum/
27
+ * exclusiveMinimum/exclusiveMaximum, multipleOf, minProperties/maxProperties,
28
+ * allOf/anyOf/oneOf/not, if/then/else, and local `$ref` (`#/$defs/…`,
29
+ * `#/definitions/…`, or any in-document JSON Pointer).
28
30
  */
29
31
 
30
32
  import crypto from "crypto";
@@ -41,6 +43,97 @@ const KNOWN_TYPES = new Set([
41
43
 
42
44
  const MAX_REF_DEPTH = 128; // guards recursive $ref against cyclic data
43
45
 
46
+ // ─── format assertions ───────────────────────────────────────────────────────
47
+ // Draft 2020-12 `format` is asserted here (the protocol-facing structured-output
48
+ // contract wants it enforced, not merely annotated). A format not in this map is
49
+ // treated as an annotation and always passes. All checkers are pure strings→bool.
50
+
51
+ function isCalendarDate(y, m, d) {
52
+ if (m < 1 || m > 12 || d < 1) return false;
53
+ const leap = (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0;
54
+ const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
55
+ return d <= days[m - 1];
56
+ }
57
+
58
+ function checkDate(v) {
59
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v);
60
+ return !!m && isCalendarDate(+m[1], +m[2], +m[3]);
61
+ }
62
+
63
+ function checkTime(v) {
64
+ const m = /^(\d{2}):(\d{2}):(\d{2})(\.\d+)?([Zz]|[+-]\d{2}:\d{2})?$/.exec(v);
65
+ if (!m) return false;
66
+ const hh = +m[1];
67
+ const mm = +m[2];
68
+ const ss = +m[3];
69
+ return hh <= 23 && mm <= 59 && ss <= 60; // 60 allows a leap second
70
+ }
71
+
72
+ function checkDateTime(v) {
73
+ const m = /^(.+)[Tt](.+)$/.exec(v);
74
+ if (!m) return false;
75
+ // RFC 3339 date-time requires an offset/Z on the time part.
76
+ if (!/([Zz]|[+-]\d{2}:\d{2})$/.test(m[2])) return false;
77
+ return checkDate(m[1]) && checkTime(m[2]);
78
+ }
79
+
80
+ function checkOctet(s) {
81
+ if (!/^\d{1,3}$/.test(s)) return false;
82
+ if (s.length > 1 && s[0] === "0") return false; // no leading zeros
83
+ return +s <= 255;
84
+ }
85
+
86
+ function checkIpv4(v) {
87
+ const parts = v.split(".");
88
+ return parts.length === 4 && parts.every(checkOctet);
89
+ }
90
+
91
+ function checkIpv6(v) {
92
+ if (typeof v !== "string" || v.length === 0) return false;
93
+ // At most one "::" compression; split on it.
94
+ const halves = v.split("::");
95
+ if (halves.length > 2) return false;
96
+ const hextet = /^[0-9a-fA-F]{1,4}$/;
97
+ if (halves.length === 2) {
98
+ const left = halves[0] === "" ? [] : halves[0].split(":");
99
+ const right = halves[1] === "" ? [] : halves[1].split(":");
100
+ if (
101
+ !left.every((g) => hextet.test(g)) ||
102
+ !right.every((g) => hextet.test(g))
103
+ )
104
+ return false;
105
+ return left.length + right.length <= 7; // "::" stands for ≥1 zero group
106
+ }
107
+ const groups = v.split(":");
108
+ return groups.length === 8 && groups.every((g) => hextet.test(g));
109
+ }
110
+
111
+ function checkHostname(v) {
112
+ if (typeof v !== "string" || v.length === 0 || v.length > 253) return false;
113
+ const labels = v.replace(/\.$/, "").split(".");
114
+ return labels.every((l) =>
115
+ /^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/.test(l),
116
+ );
117
+ }
118
+
119
+ const FORMAT_CHECKERS = {
120
+ "date-time": checkDateTime,
121
+ date: checkDate,
122
+ time: checkTime,
123
+ email: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
124
+ uri: (v) => /^[A-Za-z][A-Za-z0-9+.-]*:\S*$/.test(v),
125
+ uuid: (v) =>
126
+ /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(
127
+ v,
128
+ ),
129
+ ipv4: checkIpv4,
130
+ ipv6: checkIpv6,
131
+ hostname: checkHostname,
132
+ };
133
+
134
+ /** Known format names (exported for meta-validation / callers). */
135
+ export const KNOWN_FORMATS = new Set(Object.keys(FORMAT_CHECKERS));
136
+
44
137
  /** RFC 6901 JSON Pointer from a segment array ("" = document root). */
45
138
  export function jsonPointer(segments) {
46
139
  if (!segments || segments.length === 0) return "";
@@ -194,6 +287,32 @@ function _validate(value, schema, inst, sch, root, errors, depth) {
194
287
  _validateObject(value, schema, inst, sch, root, errors, depth);
195
288
 
196
289
  _validateCombinators(value, schema, inst, sch, root, errors, depth);
290
+ _validateConditional(value, schema, inst, sch, root, errors, depth);
291
+ }
292
+
293
+ /**
294
+ * if/then/else — Draft 2020-12 conditional application. `if` is a *test* whose
295
+ * own errors are never reported; if the value matches `if`, `then` applies,
296
+ * otherwise `else` applies. No `if` keyword ⇒ then/else are inert.
297
+ */
298
+ function _validateConditional(value, schema, inst, sch, root, errors, depth) {
299
+ if (schema.if === undefined) return;
300
+ const matched = _branchValid(value, schema.if, root, depth);
301
+ if (matched) {
302
+ if (schema.then !== undefined) {
303
+ _validate(
304
+ value,
305
+ schema.then,
306
+ inst,
307
+ [...sch, "then"],
308
+ root,
309
+ errors,
310
+ depth,
311
+ );
312
+ }
313
+ } else if (schema.else !== undefined) {
314
+ _validate(value, schema.else, inst, [...sch, "else"], root, errors, depth);
315
+ }
197
316
  }
198
317
 
199
318
  function _validateString(value, schema, inst, sch, errors) {
@@ -232,6 +351,19 @@ function _validateString(value, schema, inst, sch, errors) {
232
351
  );
233
352
  }
234
353
  }
354
+ if (typeof schema.format === "string") {
355
+ const check = FORMAT_CHECKERS[schema.format];
356
+ // Unknown format = annotation only (always passes), per Draft 2020-12.
357
+ if (check && !check(value)) {
358
+ _err(
359
+ errors,
360
+ "format",
361
+ inst,
362
+ [...sch, "format"],
363
+ `string is not a valid ${schema.format}`,
364
+ );
365
+ }
366
+ }
235
367
  }
236
368
 
237
369
  function _validateNumber(value, schema, inst, sch, errors) {
@@ -650,6 +782,14 @@ function _metaValidate(schema, segs, errors, depth) {
650
782
  }
651
783
  if (schema.not !== undefined)
652
784
  _metaValidate(schema.not, [...segs, "not"], errors, depth + 1);
785
+ if (schema.format !== undefined && typeof schema.format !== "string") {
786
+ _metaErr(errors, "format", [...segs, "format"], "format must be a string");
787
+ }
788
+ for (const k of ["if", "then", "else"]) {
789
+ if (schema[k] !== undefined) {
790
+ _metaValidate(schema[k], [...segs, k], errors, depth + 1);
791
+ }
792
+ }
653
793
  for (const defsKey of ["$defs", "definitions"]) {
654
794
  if (schema[defsKey] !== undefined) {
655
795
  if (