chainlesschain 0.166.10 → 0.166.13

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 (230) hide show
  1. package/README.md +33 -0
  2. package/bin/chainlesschain.js +5 -0
  3. package/package.json +4 -2
  4. package/src/assets/web-panel/assets/{AIOps-CLYBDAGv.js → AIOps-BN25cNOD.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-CpeSttcu.js → ActionButton-Da9IP-m5.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-Ba1TNLG0.js → Analytics-D7hkEvQm.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-xBy8EOeg.js → AppLayout-CCV4Xnbc.js} +5 -5
  8. package/src/assets/web-panel/assets/{Artifacts-CJ_3TFb8.js → Artifacts-CVSnVJeA.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-DUATFCG3.js → Audit-CuYdw5Ag.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-BRXYPIGp.js → BackgroundAgents-DZCnK-Tf.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-D8auLx7y.js → Backup-cV9FdpTy.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-CBgdslQ3.js → BaseInput-BRnv6YSp.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-B32LvAiE.js → Chat-Bw5w87P4.js} +5 -5
  14. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CVfvAy-n.js → ChatBubbleRenderer-C0GjtUAw.js} +1 -1
  15. package/src/assets/web-panel/assets/{Checkbox-BwzBxOpC.js → Checkbox-DaoTqcdt.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-0U5vrg1H.js → Codegen-B4dYcEIi.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-dZ2i4aAj.js → Col-mAhuOeuD.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-UVvpLr8c.js → Community-BK6KEdBk.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-BDNEb1Bd.js → Compact-CCglmNu5.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-BrzQPxyS.js → Compliance-Cm8S4of7.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-BbgGsu-9.js → Cowork-Dhs1QLMe.js} +2 -2
  22. package/src/assets/web-panel/assets/{Cron-G321XPhp.js → Cron-CLlhmXWk.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-CgLNUyMn.js → Crosschain-BnHVbaNs.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-BAfRj18q.js → DID-UzQP9-0O.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-BNIxA4TK.js → Dashboard-CBdlPClz.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-BLAUmCAN.js → Dropdown-axIRPdq1.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-CsnhTW22.js → EmailListRenderer-DUalW5rx.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-zcQ7Ip1n.js → FamilyGuardDashboard-Bl4W9luY.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-KFwxodNU.js → Federation-C1yP-uEu.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-3CYRLEHG.js → FormItemContext-Xh6Ig5q8.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-D3d9D8L9.js → GenericCardRenderer-C2cz3bt2.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-BJ202nJY.js → Git-C_z63ixg.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-ClnnXVQ-.js → Governance-BYqB33_3.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-DNU-PTkc.js → Inference-CEULuArm.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-DJuinbuY.js → KnowledgeGraph-fgMsu-0H.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-D4No-46s.js → Logs-DO9Hdcec.js} +2 -2
  37. package/src/assets/web-panel/assets/MarkdownRenderer-BkrK04xW.js +1 -0
  38. package/src/assets/web-panel/assets/{Marketplace-Ba-KqRN3.js → Marketplace-B8zyLiFe.js} +1 -1
  39. package/src/assets/web-panel/assets/{McpTools-DVoxnPgE.js → McpTools-BLAN2mWW.js} +3 -3
  40. package/src/assets/web-panel/assets/{Memory-D_C8gzAZ.js → Memory-CrQcFM7u.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-DnzTPcDH.js → MobileBridge-DVnyiiU-.js} +1 -1
  42. package/src/assets/web-panel/assets/MobileProjects-qjCME5OD.js +1 -0
  43. package/src/assets/web-panel/assets/{Mtc-CvEHmHId.js → Mtc-4JE3C3It.js} +2 -2
  44. package/src/assets/web-panel/assets/{MtcAudit-VNJmWqmv.js → MtcAudit-CjBuak9I.js} +5 -5
  45. package/src/assets/web-panel/assets/{Multisig-BO0x_Vsa.js → Multisig-BtPcYQcC.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-CM2gI1UY.js → NLProgramming-Cx653_Ba.js} +1 -1
  47. package/src/assets/web-panel/assets/{Notes-CMc1HsML.js → Notes-CkuD3YzS.js} +3 -3
  48. package/src/assets/web-panel/assets/{NotificationSettings-DGNmSKGM.js → NotificationSettings-BgNgU7HH.js} +1 -1
  49. package/src/assets/web-panel/assets/{OrderTableRenderer-Du-Kct3p.js → OrderTableRenderer-BTC8W0a-.js} +1 -1
  50. package/src/assets/web-panel/assets/{Organization-DRtRZjas.js → Organization-Dmd4Hjxz.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-Dp4OeWsn.js → Overflow-C_5DA-G0.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-DXMJVls8.js → P2P-uwslV4XC.js} +2 -2
  53. package/src/assets/web-panel/assets/PdhVaultBrowser-CuNgwVnX.js +7 -0
  54. package/src/assets/web-panel/assets/{Permissions-D1Hbb51o.js → Permissions-CJayVX9H.js} +4 -4
  55. package/src/assets/web-panel/assets/{PersonalDataHub-BridiDhr.js → PersonalDataHub-Cb05H5go.js} +4 -4
  56. package/src/assets/web-panel/assets/{Pipeline-B4bUNCm1.js → Pipeline-BZlGKapZ.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-BTu_HEqK.js → Privacy-BIzZWYzd.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-SVtL2hnt.js → ProjectInit-vEYYyQXQ.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-B2Sh7JZ2.js → ProjectSettings-MZqTy5CC.js} +2 -2
  60. package/src/assets/web-panel/assets/{Projects-BwU1xalW.js → Projects-CRARUOl8.js} +1 -1
  61. package/src/assets/web-panel/assets/{Providers-BWdWQSDw.js → Providers-80VAK8PX.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-Brzc-Sxb.js → QrScannerModal-CzRSoIwK.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-DIBz7FCe.js → QuickAsk-BpwQ2n4C.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-CZ8RriIJ.js → Recommend-BC03TZ79.js} +1 -1
  65. package/src/assets/web-panel/assets/{RemoteSession-CM0KJQ3t.js → RemoteSession-DE59-jaa.js} +2 -2
  66. package/src/assets/web-panel/assets/{Reputation-D_wo8H0O.js → Reputation-DOHmeWyA.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-D1G1fD7N.js → Row-hc5RZl1D.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-dKCVtA0K.js → RssFeed-Cv5uMP7E.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-Be1D61xo.js → Search-qohu7Ug0.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-IbNiR6OC.js → Security-drCPFWV5.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-nuHK8y8p.js → Services-BAEgUeRy.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-DnM5jdnC.js → Skeleton-CkJkKBlo.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-DCTdoq_q.js → Skills-bMuKEpez.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-DlWun-Ui.js → Sla-DxWQVHRC.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-C19kv2sd.js → SpeechSettings-BpS8idmV.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-CkQIR_Hu.js → SyncSettings-BPBLlW5v.js} +2 -2
  77. package/src/assets/web-panel/assets/{Tasks-DdbqOEbs.js → Tasks-C4SLEeUh.js} +1 -1
  78. package/src/assets/web-panel/assets/{Templates-B7GpIXeo.js → Templates-r-wfU39U.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-ByEstFhY.js → Tenant-C7vjRmLN.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-BKRVID1a.js → Terminal-DdzeFCV2.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-CNS3dHNv.js → TimelineRenderer-CYyWN2KD.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-BykqylCL.js → Tokens-D8A9osUg.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-ByNxq51W.js → Trigger-DBvrNlyS.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-BlrFsdaS.js → Trust-DBVAGc7o.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-Dg26A-sd.js → UkeySign-C1vQUCRJ.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-C1wUxNn0.js → VideoEditing-BKs4dah4.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-OphEUzI4.js → Wallet-ByIuj3lD.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-_8lL08hF.js → WebAuthn-DYlAFCm9.js} +3 -3
  89. package/src/assets/web-panel/assets/{WorkflowEditor-BnTuIvnP.js → WorkflowEditor-Dwyadd0c.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-3N3VLQYr.js → chat-Bm2f3s08.js} +1 -1
  91. package/src/assets/web-panel/assets/{colors-Bo_G96gg.js → colors-C8ORSh7V.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-DjubfjI_.js → compact-item-1iVlnsM-.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-Cx_HSLCk.js → createContext-CmqNyUSL.js} +1 -1
  94. package/src/assets/web-panel/assets/devWarning-B69RCDDH.js +1 -0
  95. package/src/assets/web-panel/assets/{hasIn-D5OjAOlA.js → hasIn-rUMaX3Ul.js} +1 -1
  96. package/src/assets/web-panel/assets/index-9biMQ-tw.js +1 -0
  97. package/src/assets/web-panel/assets/{index-Bj7S5IJw.js → index-B53c3diB.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-C-_Cy0k_.js → index-B59OWAs7.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DaU60K3A.js → index-B8BrB0Ls.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-UnpsQ6A0.js → index-BC32_0xT.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BScGr1BY.js → index-BC8Wb6cy.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CC7RnaPP.js → index-BCOhTILd.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DXJqa1-I.js → index-BFniLMn9.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-C-4sfa0l.js → index-BIvM-uAX.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-C8dPFFhy.js → index-BK-RY0-7.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CVPDhmVH.js → index-BQL9r73Y.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CqZq3zWr.js → index-BVtnizyC.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-C5IwN9q3.js → index-Bf1PlIHp.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Cz5tQ5PY.js → index-Bgw4Y2rU.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CmLwI-Y8.js → index-BgwLNxmr.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Y-H7j25u.js → index-C-J5-pGH.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-ydnEPOzZ.js → index-C-ZIHi42.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-HioDjvUF.js → index-C3B1S-mW.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-CRKk1JPX.js → index-C5RfRciU.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-x7o-2T3S.js → index-CCEyz58d.js} +1 -1
  116. package/src/assets/web-panel/assets/index-COie87kl.js +1 -0
  117. package/src/assets/web-panel/assets/{index-BvYo09C0.js → index-CTfEmKK0.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CCjq3g32.js → index-CfiHyVyl.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-3_CEBEEf.js → index-CkFvuAex.js} +3 -3
  120. package/src/assets/web-panel/assets/{index-EZr3bvz2.js → index-D98FXb0w.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-C4MAnkl5.js → index-DDHk911Q.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BgPb37tI.js → index-DJxtPFOh.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CNmsP0Xf.js → index-DVJ1uKf4.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DBvZhdCb.js → index-Dcck8QfW.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-B905-Mcs.js → index-DgUE51Pz.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DrVjatPe.js → index-Dsve5Gkv.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-uAcJUJkx.js → index-DvedhJeM.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DD1zSPZH.js → index-DwMdWKiF.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BxgIE4PD.js → index-OcbzdRpT.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Ce8EWU6r.js → index-fPK6GFEg.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DlcbE0sX.js → index-geQinnns.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-Dbdr8tVi.js → index-pHWA7bmM.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-DE4XtzMl.js → index-reUHhuz8.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-Q_vn0t8d.js → index-tqTx5wXo.js} +1 -1
  135. package/src/assets/web-panel/assets/{initDefaultProps-DurpENJY.js → initDefaultProps-CuA-cpkL.js} +1 -1
  136. package/src/assets/web-panel/assets/{motion-CIh-z-gR.js → motion-CimmE_9b.js} +1 -1
  137. package/src/assets/web-panel/assets/{move-Uc_yI2NA.js → move-DlGlrltz.js} +1 -1
  138. package/src/assets/web-panel/assets/{mtc-parser-DUevj5wz.js → mtc-parser-CK6BJQ07.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-85eMYyur.js → omit-BKjBfZfB.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-AuxDD20F.js → pickAttrs-B2chVYw9.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-DOWn8dd1.js → placementArrow-BW8aLJJ3.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-CPxTWodH.js → responsiveObserve-dXlPl66S.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-BqvbGPjx.js → slide-hSa89S4G.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-BzXKkV8d.js → statusUtils-DM6KLt-s.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-B2O7yRk3.js → styleChecker-C9MQAK15.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-DRWp7px1.js → useFlexGapSupport-BpQDpP0z.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs-Dp0V7sjM.js → useFs-C7rtRViA.js} +1 -1
  148. package/src/assets/web-panel/assets/{usePersonalDataHub-CyCrG3ql.js → usePersonalDataHub-Dm15xafq.js} +1 -1
  149. package/src/assets/web-panel/assets/{vnode-CXhoFpFo.js → vnode-mEiEytw4.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-0z3mtwEg.js → zoom-DzPxflhY.js} +1 -1
  151. package/src/assets/web-panel/index.html +1 -1
  152. package/src/command-help-index.json +5 -5
  153. package/src/commands/agent.js +51 -20
  154. package/src/commands/compact.js +349 -3
  155. package/src/commands/context.js +72 -1
  156. package/src/commands/graph.js +26 -15
  157. package/src/commands/hmemory.js +21 -0
  158. package/src/commands/hook.js +256 -28
  159. package/src/commands/memory.js +209 -42
  160. package/src/commands/promcomp.js +17 -0
  161. package/src/commands/serve.js +94 -10
  162. package/src/commands/session.js +2 -3
  163. package/src/commands/team.js +7 -1
  164. package/src/data/changelog.json +28 -2
  165. package/src/gateways/ws/session-core-protocol.js +62 -3
  166. package/src/gateways/ws/session-protocol.js +123 -15
  167. package/src/gateways/ws/ws-agent-handler.js +157 -9
  168. package/src/gateways/ws/ws-session-gateway.js +204 -30
  169. package/src/generated/cc-agent-protocol.schema.json +2094 -4
  170. package/src/harness/prompt-compressor.js +5 -55
  171. package/src/lib/agent-sandbox.js +14 -4
  172. package/src/lib/agent-team/team-distributed-graph-bridge.js +3 -3
  173. package/src/lib/app-server/context-memory-notifications.js +54 -0
  174. package/src/lib/app-server/rollout-store-factory.js +115 -0
  175. package/src/lib/app-server/rollout-store.js +310 -23
  176. package/src/lib/app-server/server.js +184 -7
  177. package/src/lib/app-server/sqlite-rollout-store.js +59 -16
  178. package/src/lib/auto-mode-config.js +26 -0
  179. package/src/lib/causal-observability.js +13 -1
  180. package/src/lib/context-memory-kernel/authority.js +95 -0
  181. package/src/lib/context-memory-kernel/durable-memory-port.js +296 -0
  182. package/src/lib/context-memory-kernel/index.js +8 -0
  183. package/src/lib/context-memory-kernel/jsonl-session-context-port.js +232 -0
  184. package/src/lib/context-memory-kernel/live-compaction.js +375 -0
  185. package/src/lib/context-memory-kernel/memory-service.js +667 -0
  186. package/src/lib/context-memory-kernel/message-adapter.js +451 -0
  187. package/src/lib/context-memory-kernel/privacy-purge-port.js +244 -0
  188. package/src/lib/context-memory-kernel/provider-context.js +192 -0
  189. package/src/lib/context-memory-kernel/runtime.js +61 -0
  190. package/src/lib/direct-model-usage.js +22 -18
  191. package/src/lib/graph-kernel/cutover-authority-resolver.js +4 -5
  192. package/src/lib/graph-kernel/cutover-ledger.js +2 -2
  193. package/src/lib/graph-kernel/event-store.js +3 -5
  194. package/src/lib/graph-kernel/migration-saga.js +2 -2
  195. package/src/lib/graph-kernel/production-cutover-evidence.js +8 -1
  196. package/src/lib/hierarchical-memory.js +22 -0
  197. package/src/lib/hook-audit-store.js +170 -0
  198. package/src/lib/hook-event-bus.cjs +0 -0
  199. package/src/lib/hook-manager.js +25 -83
  200. package/src/lib/hook-runtime-adapters.js +233 -0
  201. package/src/lib/hook-runtime-contract.cjs +291 -0
  202. package/src/lib/hook-runtime-contract.js +21 -0
  203. package/src/lib/hook-trust.js +210 -0
  204. package/src/lib/hooks-v2-producers.js +52 -6
  205. package/src/lib/hooks-v2-runtime.js +443 -178
  206. package/src/lib/llm-pricing.js +10 -0
  207. package/src/lib/memory-manager.js +29 -4
  208. package/src/lib/model-context-window.js +52 -0
  209. package/src/lib/permanent-memory.js +17 -0
  210. package/src/lib/plan-mode.js +8 -4
  211. package/src/lib/process-execution-broker/index.js +208 -16
  212. package/src/lib/runtime-usage-ledger.js +20 -0
  213. package/src/lib/session-consolidator.js +59 -0
  214. package/src/lib/session-core-singletons.js +17 -4
  215. package/src/lib/session-hooks.js +29 -2
  216. package/src/lib/session-transcript-migration.js +188 -0
  217. package/src/lib/settings-hook-events.cjs +37 -401
  218. package/src/lib/settings-hook-events.js +210 -8
  219. package/src/lib/settings-hooks.cjs +42 -83
  220. package/src/lib/ws-session-manager.js +4 -1
  221. package/src/repl/agent-repl.js +186 -53
  222. package/src/runtime/agent-core.js +376 -350
  223. package/src/runtime/headless-runner.js +40 -33
  224. package/src/runtime/headless-stream.js +131 -30
  225. package/src/assets/web-panel/assets/MarkdownRenderer-Dt8hH_qA.js +0 -1
  226. package/src/assets/web-panel/assets/MobileProjects-Jg2dVUc-.js +0 -1
  227. package/src/assets/web-panel/assets/PdhVaultBrowser-0jYeVmyY.js +0 -7
  228. package/src/assets/web-panel/assets/devWarning-BX944k9E.js +0 -1
  229. package/src/assets/web-panel/assets/index-C_9Jt_SR.js +0 -1
  230. package/src/assets/web-panel/assets/index-DL2EFx7m.js +0 -1
@@ -37,9 +37,20 @@ import {
37
37
  resolveRegisteredHostHooksV2Workspace,
38
38
  runWithHostHooksV2Workspace,
39
39
  } from "./hooks-v2-workspace-context.js";
40
+ import canonicalHookContract from "./hook-runtime-contract.js";
41
+ import { assessHookTrust, computeHookDefinitionDigest } from "./hook-trust.js";
42
+ import { getDefaultHookAuditStore } from "./hook-audit-store.js";
40
43
 
41
44
  const { globToRegExp } = permissionRules;
42
45
  const { buildManagedHookEnvironment } = hookEnvironment;
46
+ const {
47
+ HOOK_EXECUTION_MODE,
48
+ HOOK_PRIORITY,
49
+ normalizeHookExecutionMode,
50
+ normalizeHookPriority,
51
+ normalizeHookTimeoutMs,
52
+ validateHookEvent,
53
+ } = canonicalHookContract;
43
54
  const {
44
55
  buildExplicitHookShellInvocation,
45
56
  issueTrustedHookSandboxContract,
@@ -49,57 +60,9 @@ const {
49
60
  sandboxBoundaryError,
50
61
  } = hookShellCommand;
51
62
 
52
- const VALID_HOOK_EVENTS = new Set([
53
- "Setup",
54
- // Session
55
- "PreToolUse",
56
- "PostToolUse",
57
- "Notification",
58
- "Stop",
59
- "SubagentStart",
60
- "SubagentStop",
61
- "SessionResume",
62
- "SessionPause",
63
- "PostCompact",
64
- "StopFailure",
65
- // Auth
66
- "PreCommit",
67
- "PostCommit",
68
- // Skill
69
- "UserPromptSubmit",
70
- "UserPromptExpansion",
71
- "SessionStart",
72
- "SessionEnd",
73
- // Model
74
- "PreCompact",
75
- "ModelSelection",
76
- // Config
77
- "ConfigChange",
78
- "PermissionAllow",
79
- "PermissionDeny",
80
- "PermissionRequest",
81
- "PermissionDenied",
82
- // Timeline
83
- "TimelineEntry",
84
- // MCP
85
- "McpRequest",
86
- "McpResponse",
87
- "MCPElicitation",
88
- "Elicitation",
89
- "ElicitationResult",
90
- // Task / workspace lifecycle
91
- "TaskCreated",
92
- "TaskCompleted",
93
- "InstructionsLoaded",
94
- "CwdChanged",
95
- "WorktreeCreate",
96
- "WorktreeRemove",
97
- "TeammateIdle",
98
- // Tool aggregate / failure / filesystem lifecycle
99
- "PostToolUseFailure",
100
- "PostToolBatch",
101
- "FileChanged",
102
- ]);
63
+ const VALID_HOOK_EVENTS = new Set(
64
+ Object.values(canonicalHookContract.HOOK_EVENT_TYPES),
65
+ );
103
66
 
104
67
  const VALID_EXECUTOR_TYPES = new Set([
105
68
  "command",
@@ -112,49 +75,9 @@ const VALID_EXECUTOR_TYPES = new Set([
112
75
  "js",
113
76
  ]);
114
77
 
115
- export const HOOK_EVENT_SCHEMA_VERSION = 1;
116
- const DECISION_EVENTS = new Set([
117
- "Setup",
118
- "PreToolUse",
119
- "UserPromptSubmit",
120
- "UserPromptExpansion",
121
- "PermissionRequest",
122
- "ModelSelection",
123
- "PreCompact",
124
- ]);
125
- const CONTEXT_EVENTS = new Set([
126
- "Setup",
127
- "UserPromptSubmit",
128
- "UserPromptExpansion",
129
- "SessionStart",
130
- "PostCompact",
131
- ]);
132
- const DEFAULT_ALLOWED_EXECUTORS = Object.freeze([
133
- "command",
134
- "http",
135
- "mcp_tool",
136
- "prompt",
137
- "agent",
138
- "js",
139
- ]);
140
-
141
- export const HOOK_EVENT_CONTRACTS = Object.freeze(
142
- Object.fromEntries(
143
- [...VALID_HOOK_EVENTS].map((event) => [
144
- event,
145
- Object.freeze({
146
- schemaVersion: HOOK_EVENT_SCHEMA_VERSION,
147
- event,
148
- allowedExecutors: DEFAULT_ALLOWED_EXECUTORS,
149
- decisionCapable: DECISION_EVENTS.has(event),
150
- contextCapable: CONTEXT_EVENTS.has(event),
151
- blockingSemantics: DECISION_EVENTS.has(event)
152
- ? "strictest:block>ask>allow>continue"
153
- : "observe-only",
154
- }),
155
- ]),
156
- ),
157
- );
78
+ export const HOOK_EVENT_SCHEMA_VERSION =
79
+ canonicalHookContract.HOOK_EVENT_SCHEMA_VERSION;
80
+ export const HOOK_EVENT_CONTRACTS = canonicalHookContract.HOOK_EVENT_CONTRACTS;
158
81
 
159
82
  const DECISION_RANK = Object.freeze({
160
83
  continue: 0,
@@ -165,9 +88,58 @@ const DECISION_RANK = Object.freeze({
165
88
 
166
89
  function normalizeDecision(value) {
167
90
  const decision = String(value || "continue").toLowerCase();
91
+ if (["deny", "denied", "reject", "rejected"].includes(decision)) {
92
+ return "block";
93
+ }
94
+ if (["approve", "approved", "accept"].includes(decision)) return "allow";
95
+ if (["prompt", "confirm"].includes(decision)) return "ask";
168
96
  return Object.hasOwn(DECISION_RANK, decision) ? decision : "continue";
169
97
  }
170
98
 
99
+ function decisionFromHookResult(result) {
100
+ return normalizeDecision(
101
+ result?.decision ||
102
+ result?.permissionDecision ||
103
+ result?.hookSpecificOutput?.permissionDecision ||
104
+ result?.hookSpecificOutput?.decision,
105
+ );
106
+ }
107
+
108
+ function normalizeHookDefinition(def, authority = null) {
109
+ if (!def || typeof def !== "object") {
110
+ throw new TypeError("Hook definition must be an object");
111
+ }
112
+ if (!VALID_HOOK_EVENTS.has(def.event)) {
113
+ throw new Error(`Invalid event: ${def.event}`);
114
+ }
115
+ if (!VALID_EXECUTOR_TYPES.has(def.type)) {
116
+ throw new Error(`Invalid executor type: ${def.type}`);
117
+ }
118
+ const contract = HOOK_EVENT_CONTRACTS[def.event];
119
+ if (!contract.allowedExecutors.includes(def.type)) {
120
+ throw new Error(
121
+ `Executor ${def.type} is not allowed for event ${def.event}`,
122
+ );
123
+ }
124
+ const normalized = {
125
+ blocking: false,
126
+ ...def,
127
+ id: def.id || crypto.randomUUID(),
128
+ priority: normalizeHookPriority(def.priority, HOOK_PRIORITY.NORMAL),
129
+ timeoutMs: normalizeHookTimeoutMs(def.timeoutMs),
130
+ executionMode: normalizeHookExecutionMode(def.executionMode ?? def.async),
131
+ authority: def.authority ||
132
+ authority || {
133
+ kind: "programmatic",
134
+ scope: "explicit",
135
+ requiresConsent: false,
136
+ },
137
+ };
138
+ normalized.definitionDigest =
139
+ def.definitionDigest || computeHookDefinitionDigest(normalized);
140
+ return normalized;
141
+ }
142
+
171
143
  function strictestDecision(results) {
172
144
  let decision = "continue";
173
145
  for (const record of results) {
@@ -318,7 +290,11 @@ export function assertManagedHookPolicy(
318
290
  }
319
291
 
320
292
  if (hook.type === "command") {
321
- if (hook.shell === true && policy.allowShell !== true) {
293
+ if (
294
+ hook.shell === true &&
295
+ policy.allowShell !== true &&
296
+ hook.legacyAdapter !== true
297
+ ) {
322
298
  throw managedPolicyError(
323
299
  "Shell-mode command hooks are disabled by managed policy",
324
300
  );
@@ -428,10 +404,7 @@ function hookBudget(hook) {
428
404
  32768,
429
405
  Math.max(1, Number(hook.maxTokens || hook.tokenBudget) || 4096),
430
406
  ),
431
- timeoutMs: Math.min(
432
- 10 * 60 * 1000,
433
- Math.max(1, Number(hook.timeoutMs) || 30000),
434
- ),
407
+ timeoutMs: normalizeHookTimeoutMs(hook.timeoutMs),
435
408
  });
436
409
  }
437
410
 
@@ -474,6 +447,12 @@ class HooksV2Runtime extends EventEmitter {
474
447
  this.workspaceRoot =
475
448
  this.workspaceBinding?.workspaceRoot || configuredWorkspaceRoot;
476
449
  this.durableStore = options.durableStore || null;
450
+ const productionAuditRequired = process.env.NODE_ENV !== "test";
451
+ this.auditStore =
452
+ options.auditStore ||
453
+ (productionAuditRequired ? getDefaultHookAuditStore() : null);
454
+ this.requireAudit =
455
+ productionAuditRequired || options.requireAudit === true;
477
456
  this.durableOwner =
478
457
  options.durableOwner ||
479
458
  `hooks-inline:${process.pid}:${crypto.randomUUID()}`;
@@ -505,6 +484,24 @@ class HooksV2Runtime extends EventEmitter {
505
484
  this._loaded = false;
506
485
  }
507
486
 
487
+ _appendAudit(record, { required = this.requireAudit } = {}) {
488
+ if (!this.auditStore) {
489
+ if (required) {
490
+ const error = new Error("Canonical Hook audit store is unavailable");
491
+ error.code = "CC_HOOK_AUDIT_UNAVAILABLE";
492
+ throw error;
493
+ }
494
+ return null;
495
+ }
496
+ try {
497
+ return this.auditStore.append(record);
498
+ } catch (error) {
499
+ this.emit("audit:error", error);
500
+ if (required) throw error;
501
+ return null;
502
+ }
503
+ }
504
+
508
505
  setDurableStore(store) {
509
506
  this.durableStore = store || null;
510
507
  return this.durableStore;
@@ -537,6 +534,11 @@ class HooksV2Runtime extends EventEmitter {
537
534
  const content = await fs.readFile(hooksPath, "utf8");
538
535
  const config = JSON.parse(content);
539
536
  this.hooks.clear();
537
+ const sourceFile = path.resolve(hooksPath);
538
+ const sourceDigest = crypto
539
+ .createHash("sha256")
540
+ .update(content, "utf8")
541
+ .digest("hex");
540
542
 
541
543
  const hookDefs = Array.isArray(config) ? config : config.hooks || [];
542
544
  for (const def of hookDefs) {
@@ -552,9 +554,18 @@ class HooksV2Runtime extends EventEmitter {
552
554
  );
553
555
  continue;
554
556
  }
555
- const id = def.id || crypto.randomUUID();
557
+ const normalized = normalizeHookDefinition(
558
+ { ...def, id: def.id || crypto.randomUUID() },
559
+ {
560
+ kind: "config",
561
+ scope: "project",
562
+ sourceFile,
563
+ digest: sourceDigest,
564
+ requiresConsent: true,
565
+ },
566
+ );
556
567
  if (!this.hooks.has(def.event)) this.hooks.set(def.event, []);
557
- this.hooks.get(def.event).push({ id, ...def });
568
+ this.hooks.get(def.event).push(normalized);
558
569
  }
559
570
  this._loaded = true;
560
571
  this.emit("loaded", {
@@ -572,25 +583,15 @@ class HooksV2Runtime extends EventEmitter {
572
583
  * Register a hook programmatically
573
584
  */
574
585
  registerHook(def) {
575
- if (!VALID_HOOK_EVENTS.has(def.event))
576
- throw new Error(`Invalid event: ${def.event}`);
577
- if (!VALID_EXECUTOR_TYPES.has(def.type))
578
- throw new Error(`Invalid executor type: ${def.type}`);
579
- const contract = HOOK_EVENT_CONTRACTS[def.event];
580
- if (!contract.allowedExecutors.includes(def.type)) {
581
- throw new Error(
582
- `Executor ${def.type} is not allowed for event ${def.event}`,
583
- );
584
- }
585
- const id = def.id || crypto.randomUUID();
586
- const full = { id, blocking: false, timeoutMs: 30000, ...def };
586
+ const full = normalizeHookDefinition(def);
587
+ const id = full.id;
587
588
  if (!this.hooks.has(def.event)) this.hooks.set(def.event, []);
588
589
  this.hooks.get(def.event).push(full);
589
590
  return id;
590
591
  }
591
592
 
592
593
  unregisterHook(id) {
593
- for (const [event, hooks] of this.hooks.entries()) {
594
+ for (const hooks of this.hooks.values()) {
594
595
  const idx = hooks.findIndex((h) => h.id === id);
595
596
  if (idx >= 0) {
596
597
  hooks.splice(idx, 1);
@@ -607,11 +608,26 @@ class HooksV2Runtime extends EventEmitter {
607
608
  * @returns {Promise<{results: Array, blocked: boolean, blockingResult?: any}>}
608
609
  */
609
610
  async emitEvent(eventName, context = {}, options = {}) {
610
- if (!VALID_HOOK_EVENTS.has(eventName)) {
611
- throw new Error(`Invalid hook event: ${eventName}`);
612
- }
613
-
614
- const hooks = (this.hooks.get(eventName) || []).filter(
611
+ const contract = validateHookEvent(eventName, context);
612
+ const eventId = context.event_id || context.eventId || crypto.randomUUID();
613
+ const auditRequired = options.auditRequired === true || this.requireAudit;
614
+ this._appendAudit(
615
+ {
616
+ phase: "dispatch",
617
+ event: eventName,
618
+ eventId,
619
+ status: "accepted",
620
+ decision: "continue",
621
+ },
622
+ { required: auditRequired && contract.decisionCapable },
623
+ );
624
+ const suppliedHooks = Array.isArray(options.additionalHooks)
625
+ ? options.additionalHooks.map((hook) => normalizeHookDefinition(hook))
626
+ : [];
627
+ const hooks = [
628
+ ...(this.hooks.get(eventName) || []),
629
+ ...suppliedHooks,
630
+ ].filter(
615
631
  (hook) =>
616
632
  eventName !== "FileChanged" || fileChangedHookMatches(hook, context),
617
633
  );
@@ -640,7 +656,7 @@ class HooksV2Runtime extends EventEmitter {
640
656
  context,
641
657
  },
642
658
  {
643
- id: context.event_id || context.eventId || null,
659
+ id: eventId,
644
660
  metadata: {
645
661
  hooksV2WorkspaceBindingId:
646
662
  durableWorkspaceBinding?.bindingId || null,
@@ -676,82 +692,221 @@ class HooksV2Runtime extends EventEmitter {
676
692
  seen.add(hook.id);
677
693
  uniqueHooks.push(hook);
678
694
  }
679
- const runOne = async (hook) => {
695
+ uniqueHooks.sort(
696
+ (left, right) =>
697
+ normalizeHookPriority(left.priority) -
698
+ normalizeHookPriority(right.priority) ||
699
+ String(left.id).localeCompare(String(right.id)),
700
+ );
701
+ const runOne = async (
702
+ hook,
703
+ { forceObserveOnly = false, dispatcher = null } = {},
704
+ ) => {
680
705
  const execId = crypto.randomUUID();
681
706
  const start = Date.now();
707
+ const authorityRoot =
708
+ this.workspaceRoot || currentHostHooksV2WorkspaceRoot();
709
+ const trust = assessHookTrust(hook, { workspaceRoot: authorityRoot });
682
710
  const record = {
683
711
  execId,
684
712
  hookId: hook.id,
713
+ sourceHookId: hook.databaseHookId || hook.id,
714
+ hookName: hook.databaseHookName || hook.description || hook.id,
685
715
  event: eventName,
686
716
  type: hook.type,
717
+ priority: normalizeHookPriority(hook.priority),
718
+ executionMode: normalizeHookExecutionMode(hook.executionMode),
719
+ definitionDigest: hook.definitionDigest,
720
+ trustStatus: trust.status,
687
721
  startedAt: new Date(),
688
722
  };
689
723
 
690
724
  try {
725
+ this._appendAudit(
726
+ {
727
+ phase: "hook-start",
728
+ event: eventName,
729
+ eventId,
730
+ executionId: execId,
731
+ hookId: hook.id,
732
+ hookDigest: hook.definitionDigest,
733
+ sourceKind: trust.authority.kind,
734
+ sourceDigest: trust.authority.sourceDigest,
735
+ trustStatus: trust.status,
736
+ priority: record.priority,
737
+ executionMode: record.executionMode,
738
+ status: trust.trusted ? "running" : "untrusted",
739
+ decision: "continue",
740
+ },
741
+ { required: auditRequired && contract.decisionCapable },
742
+ );
743
+ if (!trust.trusted) {
744
+ const error = new Error(
745
+ `Hook source requires explicit approval or reapproval: ${trust.status}`,
746
+ );
747
+ error.code = "CC_HOOK_REAPPROVAL_REQUIRED";
748
+ throw error;
749
+ }
691
750
  assertManagedHookPolicy(
692
751
  hook,
693
752
  context,
694
753
  this.managedPolicy,
695
754
  this.configDir,
696
- this.workspaceRoot || currentHostHooksV2WorkspaceRoot(),
755
+ authorityRoot,
697
756
  );
698
757
  let result;
699
- switch (hook.type) {
700
- case "command":
701
- result = await this._execCommand(hook, context);
702
- break;
703
- case "http":
704
- result = await this._execHttp(hook, context);
705
- break;
706
- case "mcp_tool":
707
- result = await this._execMcpTool(hook, context);
708
- break;
709
- case "prompt":
710
- result = await this._execPrompt(hook, context);
711
- break;
712
- case "agent":
713
- result = await this._execAgent(hook, context);
714
- break;
715
- case "js":
716
- result = await this._execJs(hook, context);
717
- break;
758
+ if (typeof dispatcher === "function") {
759
+ result = await dispatcher(hook, context);
760
+ } else {
761
+ switch (hook.type) {
762
+ case "command":
763
+ result = await this._execCommand(hook, context);
764
+ break;
765
+ case "http":
766
+ result = await this._execHttp(hook, context);
767
+ break;
768
+ case "mcp_tool":
769
+ result = await this._execMcpTool(hook, context);
770
+ break;
771
+ case "prompt":
772
+ result = await this._execPrompt(hook, context);
773
+ break;
774
+ case "agent":
775
+ result = await this._execAgent(hook, context);
776
+ break;
777
+ case "js":
778
+ result = await this._execJs(hook, context);
779
+ break;
780
+ }
718
781
  }
719
782
 
720
783
  record.durationMs = Date.now() - start;
721
784
  record.status = "success";
722
785
  record.result = result;
723
- record.decision = normalizeDecision(result?.decision);
786
+ record.decision =
787
+ forceObserveOnly || hook.observeOnly === true
788
+ ? "continue"
789
+ : decisionFromHookResult(result);
724
790
 
725
791
  this.emit("hook:success", record);
726
792
  } catch (err) {
727
793
  record.durationMs = Date.now() - start;
728
794
  record.status = "error";
729
795
  record.error = err.message;
796
+ record.errorCode = err.code || null;
797
+ record.nonBlockingError = hook.failureMode === "ignore";
730
798
  record.decision =
731
- HOOK_EVENT_CONTRACTS[eventName].decisionCapable &&
799
+ contract.decisionCapable &&
800
+ !forceObserveOnly &&
801
+ hook.observeOnly !== true &&
732
802
  hook.failureMode !== "ignore"
733
803
  ? "block"
734
804
  : "continue";
735
805
  this.emit("hook:error", record);
736
806
  }
737
807
 
808
+ try {
809
+ this._appendAudit(
810
+ {
811
+ phase: "hook-result",
812
+ event: eventName,
813
+ eventId,
814
+ executionId: execId,
815
+ hookId: hook.id,
816
+ hookDigest: hook.definitionDigest,
817
+ sourceKind: trust.authority.kind,
818
+ sourceDigest: trust.authority.sourceDigest,
819
+ trustStatus: trust.status,
820
+ priority: record.priority,
821
+ executionMode: record.executionMode,
822
+ status: record.status,
823
+ decision: record.decision,
824
+ durationMs: record.durationMs,
825
+ errorCode: record.errorCode,
826
+ },
827
+ { required: auditRequired && contract.decisionCapable },
828
+ );
829
+ } catch (error) {
830
+ record.status = "error";
831
+ record.error = error.message;
832
+ record.errorCode = error.code || "CC_HOOK_AUDIT_UNAVAILABLE";
833
+ if (contract.decisionCapable && !forceObserveOnly) {
834
+ record.decision = "block";
835
+ }
836
+ }
738
837
  this.executionLog.push(record);
838
+ if (typeof hook.recordResult === "function") {
839
+ try {
840
+ hook.recordResult(record);
841
+ } catch (error) {
842
+ record.statsError = error?.message || String(error);
843
+ }
844
+ }
739
845
  return record;
740
846
  };
741
- const results =
742
- options.parallel === false
743
- ? []
744
- : await Promise.all(uniqueHooks.map(runOne));
847
+ const blockingHooks = uniqueHooks.filter(
848
+ (hook) =>
849
+ normalizeHookExecutionMode(hook.executionMode) !==
850
+ HOOK_EXECUTION_MODE.ASYNC,
851
+ );
852
+ const asyncHooks = uniqueHooks.filter(
853
+ (hook) =>
854
+ normalizeHookExecutionMode(hook.executionMode) ===
855
+ HOOK_EXECUTION_MODE.ASYNC,
856
+ );
857
+ const results = [];
745
858
  if (options.parallel === false) {
746
- for (const hook of uniqueHooks) results.push(await runOne(hook));
859
+ for (const hook of blockingHooks) results.push(await runOne(hook));
860
+ } else {
861
+ for (let index = 0; index < blockingHooks.length;) {
862
+ const priority = normalizeHookPriority(blockingHooks[index].priority);
863
+ const group = [];
864
+ while (
865
+ index < blockingHooks.length &&
866
+ normalizeHookPriority(blockingHooks[index].priority) === priority
867
+ ) {
868
+ group.push(blockingHooks[index]);
869
+ index += 1;
870
+ }
871
+ results.push(...(await Promise.all(group.map((hook) => runOne(hook)))));
872
+ }
873
+ }
874
+ for (const hook of asyncHooks) {
875
+ const queued = {
876
+ execId: crypto.randomUUID(),
877
+ hookId: hook.id,
878
+ sourceHookId: hook.databaseHookId || hook.id,
879
+ hookName: hook.databaseHookName || hook.description || hook.id,
880
+ event: eventName,
881
+ type: hook.type,
882
+ priority: normalizeHookPriority(hook.priority),
883
+ executionMode: HOOK_EXECUTION_MODE.ASYNC,
884
+ status: "queued",
885
+ decision: "continue",
886
+ deferred: true,
887
+ };
888
+ results.push(queued);
889
+ void runOne(hook, {
890
+ forceObserveOnly: true,
891
+ dispatcher:
892
+ typeof options.asyncDispatcher === "function"
893
+ ? options.asyncDispatcher
894
+ : null,
895
+ }).then((record) => {
896
+ this.emit("hook:async-settled", record);
897
+ });
747
898
  }
748
- const contract = HOOK_EVENT_CONTRACTS[eventName];
749
899
  const decision = contract.decisionCapable
750
900
  ? strictestDecision(results)
751
901
  : "continue";
752
902
  const blocking = results.filter((record) => record.decision === "block");
753
903
  const blocked = decision === "block";
754
- const blockingResult = blocking[0]?.result || null;
904
+ const blockingResult = blocking[0]
905
+ ? blocking[0].result || {
906
+ reason: blocking[0].error || null,
907
+ code: blocking[0].errorCode || null,
908
+ }
909
+ : null;
755
910
  const outcome = {
756
911
  success: !blocked && results.every((record) => record.status !== "error"),
757
912
  results,
@@ -761,6 +916,26 @@ class HooksV2Runtime extends EventEmitter {
761
916
  blockingResult,
762
917
  schemaVersion: HOOK_EVENT_SCHEMA_VERSION,
763
918
  };
919
+ try {
920
+ this._appendAudit(
921
+ {
922
+ phase: "outcome",
923
+ event: eventName,
924
+ eventId,
925
+ status: outcome.success ? "success" : "error",
926
+ decision,
927
+ },
928
+ { required: auditRequired && contract.decisionCapable },
929
+ );
930
+ } catch (error) {
931
+ outcome.success = false;
932
+ outcome.blocked = contract.decisionCapable;
933
+ outcome.decision = contract.decisionCapable ? "block" : "continue";
934
+ outcome.auditError = error.code || "CC_HOOK_AUDIT_UNAVAILABLE";
935
+ outcome.blockingResult = contract.decisionCapable
936
+ ? { reason: error.message, code: outcome.auditError }
937
+ : outcome.blockingResult;
938
+ }
764
939
  if (durableRecord) {
765
940
  const settled = this.durableStore.acknowledgeInbox(
766
941
  durableRecord.id,
@@ -860,37 +1035,119 @@ class HooksV2Runtime extends EventEmitter {
860
1035
  "trusted Linux Hooks v2 sandbox contract could not be issued",
861
1036
  );
862
1037
  }
863
- const child = await this.executionBroker.spawn(
864
- invocation.file,
865
- invocation.argv,
866
- {
867
- ...commandOptions,
868
- ...(sandboxExecutionContract ? { sandboxExecutionContract } : {}),
869
- },
1038
+ const payload = JSON.stringify(
1039
+ hook.legacyPayload === true
1040
+ ? {
1041
+ ...context,
1042
+ schema_version: HOOK_EVENT_SCHEMA_VERSION,
1043
+ hook_event_name: hook.event,
1044
+ }
1045
+ : {
1046
+ schema_version: HOOK_EVENT_SCHEMA_VERSION,
1047
+ hook_event_name: hook.event,
1048
+ context,
1049
+ },
870
1050
  );
871
- const payload = JSON.stringify({
872
- schema_version: HOOK_EVENT_SCHEMA_VERSION,
873
- hook_event_name: hook.event,
874
- context,
875
- });
876
- child.stdin?.end(payload);
877
- return new Promise((resolve, reject) => {
878
- let stdout = "",
879
- stderr = "";
880
- child.stdout?.on("data", (d) => (stdout += d));
881
- child.stderr?.on("data", (d) => (stderr += d));
882
- child.on("error", reject);
883
- child.on("exit", (code) => {
884
- if (code === 0) {
885
- let parsed = {};
1051
+ return executeWithHookTimeout("command", budget, async (signal) => {
1052
+ // `spawn()` returns a ChildProcess synchronously. Do not await it: an
1053
+ // immediately failing command can emit `error`/`exit` in the microtask
1054
+ // gap before listeners are attached, leaving the hook pending until its
1055
+ // full timeout budget expires.
1056
+ const child = this.executionBroker.spawn(
1057
+ invocation.file,
1058
+ invocation.argv,
1059
+ {
1060
+ ...commandOptions,
1061
+ ...(sandboxExecutionContract ? { sandboxExecutionContract } : {}),
1062
+ },
1063
+ );
1064
+ return new Promise((resolve, reject) => {
1065
+ let stdout = "";
1066
+ let stderr = "";
1067
+ let settled = false;
1068
+ const appendBounded = (current, chunk) =>
1069
+ `${current}${String(chunk)}`.slice(-(1024 * 1024));
1070
+ const settle = (callback, value) => {
1071
+ if (settled) return;
1072
+ settled = true;
1073
+ signal.removeEventListener("abort", onAbort);
1074
+ callback(value);
1075
+ };
1076
+ const onStdinError = (error) => {
1077
+ // Fast hooks such as `echo` can exit before Node flushes the JSON
1078
+ // payload. The resulting EPIPE belongs to the stdin stream (not the
1079
+ // ChildProcess), so without a listener it becomes an uncaught process
1080
+ // exception even though the hook has a valid exit status. Keep the
1081
+ // listener attached for any late write completion; the exit event
1082
+ // remains authoritative for an already-closed stdin pipe.
1083
+ if (
1084
+ error?.code === "EPIPE" ||
1085
+ error?.code === "ERR_STREAM_DESTROYED"
1086
+ ) {
1087
+ return;
1088
+ }
1089
+ settle(reject, error);
1090
+ };
1091
+ const onAbort = () => {
886
1092
  try {
887
- parsed = JSON.parse(stdout);
1093
+ child.kill?.();
888
1094
  } catch {
889
- parsed = { raw: stdout };
1095
+ // The timeout decision is already fail-closed.
890
1096
  }
891
- resolve({ ...parsed, exitCode: code, stderr });
892
- } else {
893
- reject(new Error(`Hook command failed with exit ${code}: ${stderr}`));
1097
+ const error = new Error(
1098
+ `command hook exceeded its ${budget.timeoutMs}ms budget`,
1099
+ );
1100
+ error.code = "CC_HOOK_BUDGET_EXCEEDED";
1101
+ settle(reject, error);
1102
+ };
1103
+ child.stdout?.on(
1104
+ "data",
1105
+ (chunk) => (stdout = appendBounded(stdout, chunk)),
1106
+ );
1107
+ child.stderr?.on(
1108
+ "data",
1109
+ (chunk) => (stderr = appendBounded(stderr, chunk)),
1110
+ );
1111
+ child.stdin?.on?.("error", onStdinError);
1112
+ child.on("error", (error) => settle(reject, error));
1113
+ // `exit` can fire before stdout/stderr have drained (observed with
1114
+ // fast command hooks on macOS). `close` is the child-process event
1115
+ // that guarantees the stdio streams are closed, so parse output only
1116
+ // after it arrives.
1117
+ child.on("close", (code) => {
1118
+ if (code === 0 || code === 2) {
1119
+ let parsed = {};
1120
+ try {
1121
+ parsed = JSON.parse(stdout);
1122
+ if (
1123
+ !parsed ||
1124
+ typeof parsed !== "object" ||
1125
+ Array.isArray(parsed)
1126
+ ) {
1127
+ parsed = { raw: stdout };
1128
+ }
1129
+ } catch {
1130
+ parsed = { raw: stdout };
1131
+ }
1132
+ if (code === 2 && decisionFromHookResult(parsed) === "continue") {
1133
+ parsed.decision = "block";
1134
+ parsed.reason =
1135
+ parsed.reason || stderr || stdout || "Hook blocked";
1136
+ }
1137
+ settle(resolve, { ...parsed, exitCode: code, stderr });
1138
+ } else {
1139
+ settle(
1140
+ reject,
1141
+ new Error(`Hook command failed with exit ${code}: ${stderr}`),
1142
+ );
1143
+ }
1144
+ });
1145
+ if (signal.aborted) onAbort();
1146
+ else signal.addEventListener("abort", onAbort, { once: true });
1147
+ try {
1148
+ child.stdin?.end(payload);
1149
+ } catch (error) {
1150
+ onStdinError(error);
894
1151
  }
895
1152
  });
896
1153
  });
@@ -1037,7 +1294,12 @@ class HooksV2Runtime extends EventEmitter {
1037
1294
  }
1038
1295
 
1039
1296
  async _execJs(hook, context) {
1040
- if (typeof hook.handler === "function") return hook.handler(context);
1297
+ if (typeof hook.handler === "function") {
1298
+ const budget = hookBudget(hook);
1299
+ return executeWithHookTimeout("js", budget, (signal) =>
1300
+ hook.handler(context, { signal, budget }),
1301
+ );
1302
+ }
1041
1303
  throw new Error(
1042
1304
  "Config-loaded JavaScript is disabled; register a trusted function handler",
1043
1305
  );
@@ -1068,12 +1330,15 @@ const defaultEventRuntimeHost =
1068
1330
  process.env.CC_EVENT_RUNTIME_DURABLE === "1"
1069
1331
  ? getDefaultEventRuntimeHost()
1070
1332
  : null;
1333
+ const requireCanonicalAudit = process.env.NODE_ENV !== "test";
1071
1334
  const hooksRuntime = new HooksV2Runtime(undefined, {
1072
1335
  durableStore:
1073
1336
  defaultEventRuntimeHost?.store ||
1074
1337
  (process.env.CC_EVENT_RUNTIME_DURABLE === "1"
1075
1338
  ? new EventRuntimeStore()
1076
1339
  : null),
1340
+ auditStore: requireCanonicalAudit ? getDefaultHookAuditStore() : null,
1341
+ requireAudit: requireCanonicalAudit,
1077
1342
  });
1078
1343
  broker._setHooksEventSink(hooksRuntime);
1079
1344