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.
- package/README.md +33 -0
- package/bin/chainlesschain.js +5 -0
- package/package.json +4 -2
- package/src/assets/web-panel/assets/{AIOps-CLYBDAGv.js → AIOps-BN25cNOD.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CpeSttcu.js → ActionButton-Da9IP-m5.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Ba1TNLG0.js → Analytics-D7hkEvQm.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-xBy8EOeg.js → AppLayout-CCV4Xnbc.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CJ_3TFb8.js → Artifacts-CVSnVJeA.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DUATFCG3.js → Audit-CuYdw5Ag.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BRXYPIGp.js → BackgroundAgents-DZCnK-Tf.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-D8auLx7y.js → Backup-cV9FdpTy.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CBgdslQ3.js → BaseInput-BRnv6YSp.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-B32LvAiE.js → Chat-Bw5w87P4.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CVfvAy-n.js → ChatBubbleRenderer-C0GjtUAw.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BwzBxOpC.js → Checkbox-DaoTqcdt.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-0U5vrg1H.js → Codegen-B4dYcEIi.js} +1 -1
- package/src/assets/web-panel/assets/{Col-dZ2i4aAj.js → Col-mAhuOeuD.js} +1 -1
- package/src/assets/web-panel/assets/{Community-UVvpLr8c.js → Community-BK6KEdBk.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BDNEb1Bd.js → Compact-CCglmNu5.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BrzQPxyS.js → Compliance-Cm8S4of7.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BbgGsu-9.js → Cowork-Dhs1QLMe.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-G321XPhp.js → Cron-CLlhmXWk.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CgLNUyMn.js → Crosschain-BnHVbaNs.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BAfRj18q.js → DID-UzQP9-0O.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BNIxA4TK.js → Dashboard-CBdlPClz.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BLAUmCAN.js → Dropdown-axIRPdq1.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CsnhTW22.js → EmailListRenderer-DUalW5rx.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-zcQ7Ip1n.js → FamilyGuardDashboard-Bl4W9luY.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-KFwxodNU.js → Federation-C1yP-uEu.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-3CYRLEHG.js → FormItemContext-Xh6Ig5q8.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-D3d9D8L9.js → GenericCardRenderer-C2cz3bt2.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BJ202nJY.js → Git-C_z63ixg.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-ClnnXVQ-.js → Governance-BYqB33_3.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DNU-PTkc.js → Inference-CEULuArm.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DJuinbuY.js → KnowledgeGraph-fgMsu-0H.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-D4No-46s.js → Logs-DO9Hdcec.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BkrK04xW.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-Ba-KqRN3.js → Marketplace-B8zyLiFe.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DVoxnPgE.js → McpTools-BLAN2mWW.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-D_C8gzAZ.js → Memory-CrQcFM7u.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DnzTPcDH.js → MobileBridge-DVnyiiU-.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-qjCME5OD.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-CvEHmHId.js → Mtc-4JE3C3It.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-VNJmWqmv.js → MtcAudit-CjBuak9I.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-BO0x_Vsa.js → Multisig-BtPcYQcC.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CM2gI1UY.js → NLProgramming-Cx653_Ba.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CMc1HsML.js → Notes-CkuD3YzS.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DGNmSKGM.js → NotificationSettings-BgNgU7HH.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-Du-Kct3p.js → OrderTableRenderer-BTC8W0a-.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DRtRZjas.js → Organization-Dmd4Hjxz.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Dp4OeWsn.js → Overflow-C_5DA-G0.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DXMJVls8.js → P2P-uwslV4XC.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-CuNgwVnX.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-D1Hbb51o.js → Permissions-CJayVX9H.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BridiDhr.js → PersonalDataHub-Cb05H5go.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-B4bUNCm1.js → Pipeline-BZlGKapZ.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BTu_HEqK.js → Privacy-BIzZWYzd.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-SVtL2hnt.js → ProjectInit-vEYYyQXQ.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-B2Sh7JZ2.js → ProjectSettings-MZqTy5CC.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BwU1xalW.js → Projects-CRARUOl8.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-BWdWQSDw.js → Providers-80VAK8PX.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Brzc-Sxb.js → QrScannerModal-CzRSoIwK.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DIBz7FCe.js → QuickAsk-BpwQ2n4C.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CZ8RriIJ.js → Recommend-BC03TZ79.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-CM0KJQ3t.js → RemoteSession-DE59-jaa.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-D_wo8H0O.js → Reputation-DOHmeWyA.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D1G1fD7N.js → Row-hc5RZl1D.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-dKCVtA0K.js → RssFeed-Cv5uMP7E.js} +3 -3
- package/src/assets/web-panel/assets/{Search-Be1D61xo.js → Search-qohu7Ug0.js} +1 -1
- package/src/assets/web-panel/assets/{Security-IbNiR6OC.js → Security-drCPFWV5.js} +4 -4
- package/src/assets/web-panel/assets/{Services-nuHK8y8p.js → Services-BAEgUeRy.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DnM5jdnC.js → Skeleton-CkJkKBlo.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DCTdoq_q.js → Skills-bMuKEpez.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DlWun-Ui.js → Sla-DxWQVHRC.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C19kv2sd.js → SpeechSettings-BpS8idmV.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CkQIR_Hu.js → SyncSettings-BPBLlW5v.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DdbqOEbs.js → Tasks-C4SLEeUh.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-B7GpIXeo.js → Templates-r-wfU39U.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-ByEstFhY.js → Tenant-C7vjRmLN.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BKRVID1a.js → Terminal-DdzeFCV2.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CNS3dHNv.js → TimelineRenderer-CYyWN2KD.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BykqylCL.js → Tokens-D8A9osUg.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-ByNxq51W.js → Trigger-DBvrNlyS.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BlrFsdaS.js → Trust-DBVAGc7o.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-Dg26A-sd.js → UkeySign-C1vQUCRJ.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-C1wUxNn0.js → VideoEditing-BKs4dah4.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-OphEUzI4.js → Wallet-ByIuj3lD.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-_8lL08hF.js → WebAuthn-DYlAFCm9.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-BnTuIvnP.js → WorkflowEditor-Dwyadd0c.js} +1 -1
- package/src/assets/web-panel/assets/{chat-3N3VLQYr.js → chat-Bm2f3s08.js} +1 -1
- package/src/assets/web-panel/assets/{colors-Bo_G96gg.js → colors-C8ORSh7V.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DjubfjI_.js → compact-item-1iVlnsM-.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-Cx_HSLCk.js → createContext-CmqNyUSL.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-B69RCDDH.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-D5OjAOlA.js → hasIn-rUMaX3Ul.js} +1 -1
- package/src/assets/web-panel/assets/index-9biMQ-tw.js +1 -0
- package/src/assets/web-panel/assets/{index-Bj7S5IJw.js → index-B53c3diB.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-_Cy0k_.js → index-B59OWAs7.js} +1 -1
- package/src/assets/web-panel/assets/{index-DaU60K3A.js → index-B8BrB0Ls.js} +1 -1
- package/src/assets/web-panel/assets/{index-UnpsQ6A0.js → index-BC32_0xT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BScGr1BY.js → index-BC8Wb6cy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CC7RnaPP.js → index-BCOhTILd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DXJqa1-I.js → index-BFniLMn9.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-4sfa0l.js → index-BIvM-uAX.js} +1 -1
- package/src/assets/web-panel/assets/{index-C8dPFFhy.js → index-BK-RY0-7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CVPDhmVH.js → index-BQL9r73Y.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqZq3zWr.js → index-BVtnizyC.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5IwN9q3.js → index-Bf1PlIHp.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cz5tQ5PY.js → index-Bgw4Y2rU.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmLwI-Y8.js → index-BgwLNxmr.js} +1 -1
- package/src/assets/web-panel/assets/{index-Y-H7j25u.js → index-C-J5-pGH.js} +1 -1
- package/src/assets/web-panel/assets/{index-ydnEPOzZ.js → index-C-ZIHi42.js} +1 -1
- package/src/assets/web-panel/assets/{index-HioDjvUF.js → index-C3B1S-mW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CRKk1JPX.js → index-C5RfRciU.js} +1 -1
- package/src/assets/web-panel/assets/{index-x7o-2T3S.js → index-CCEyz58d.js} +1 -1
- package/src/assets/web-panel/assets/index-COie87kl.js +1 -0
- package/src/assets/web-panel/assets/{index-BvYo09C0.js → index-CTfEmKK0.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCjq3g32.js → index-CfiHyVyl.js} +1 -1
- package/src/assets/web-panel/assets/{index-3_CEBEEf.js → index-CkFvuAex.js} +3 -3
- package/src/assets/web-panel/assets/{index-EZr3bvz2.js → index-D98FXb0w.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4MAnkl5.js → index-DDHk911Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-BgPb37tI.js → index-DJxtPFOh.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNmsP0Xf.js → index-DVJ1uKf4.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBvZhdCb.js → index-Dcck8QfW.js} +1 -1
- package/src/assets/web-panel/assets/{index-B905-Mcs.js → index-DgUE51Pz.js} +1 -1
- package/src/assets/web-panel/assets/{index-DrVjatPe.js → index-Dsve5Gkv.js} +1 -1
- package/src/assets/web-panel/assets/{index-uAcJUJkx.js → index-DvedhJeM.js} +1 -1
- package/src/assets/web-panel/assets/{index-DD1zSPZH.js → index-DwMdWKiF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxgIE4PD.js → index-OcbzdRpT.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ce8EWU6r.js → index-fPK6GFEg.js} +1 -1
- package/src/assets/web-panel/assets/{index-DlcbE0sX.js → index-geQinnns.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dbdr8tVi.js → index-pHWA7bmM.js} +1 -1
- package/src/assets/web-panel/assets/{index-DE4XtzMl.js → index-reUHhuz8.js} +1 -1
- package/src/assets/web-panel/assets/{index-Q_vn0t8d.js → index-tqTx5wXo.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DurpENJY.js → initDefaultProps-CuA-cpkL.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CIh-z-gR.js → motion-CimmE_9b.js} +1 -1
- package/src/assets/web-panel/assets/{move-Uc_yI2NA.js → move-DlGlrltz.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DUevj5wz.js → mtc-parser-CK6BJQ07.js} +1 -1
- package/src/assets/web-panel/assets/{omit-85eMYyur.js → omit-BKjBfZfB.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-AuxDD20F.js → pickAttrs-B2chVYw9.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DOWn8dd1.js → placementArrow-BW8aLJJ3.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CPxTWodH.js → responsiveObserve-dXlPl66S.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BqvbGPjx.js → slide-hSa89S4G.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BzXKkV8d.js → statusUtils-DM6KLt-s.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-B2O7yRk3.js → styleChecker-C9MQAK15.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DRWp7px1.js → useFlexGapSupport-BpQDpP0z.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Dp0V7sjM.js → useFs-C7rtRViA.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CyCrG3ql.js → usePersonalDataHub-Dm15xafq.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CXhoFpFo.js → vnode-mEiEytw4.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-0z3mtwEg.js → zoom-DzPxflhY.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +5 -5
- package/src/commands/agent.js +51 -20
- package/src/commands/compact.js +349 -3
- package/src/commands/context.js +72 -1
- package/src/commands/graph.js +26 -15
- package/src/commands/hmemory.js +21 -0
- package/src/commands/hook.js +256 -28
- package/src/commands/memory.js +209 -42
- package/src/commands/promcomp.js +17 -0
- package/src/commands/serve.js +94 -10
- package/src/commands/session.js +2 -3
- package/src/commands/team.js +7 -1
- package/src/data/changelog.json +28 -2
- package/src/gateways/ws/session-core-protocol.js +62 -3
- package/src/gateways/ws/session-protocol.js +123 -15
- package/src/gateways/ws/ws-agent-handler.js +157 -9
- package/src/gateways/ws/ws-session-gateway.js +204 -30
- package/src/generated/cc-agent-protocol.schema.json +2094 -4
- package/src/harness/prompt-compressor.js +5 -55
- package/src/lib/agent-sandbox.js +14 -4
- package/src/lib/agent-team/team-distributed-graph-bridge.js +3 -3
- package/src/lib/app-server/context-memory-notifications.js +54 -0
- package/src/lib/app-server/rollout-store-factory.js +115 -0
- package/src/lib/app-server/rollout-store.js +310 -23
- package/src/lib/app-server/server.js +184 -7
- package/src/lib/app-server/sqlite-rollout-store.js +59 -16
- package/src/lib/auto-mode-config.js +26 -0
- package/src/lib/causal-observability.js +13 -1
- package/src/lib/context-memory-kernel/authority.js +95 -0
- package/src/lib/context-memory-kernel/durable-memory-port.js +296 -0
- package/src/lib/context-memory-kernel/index.js +8 -0
- package/src/lib/context-memory-kernel/jsonl-session-context-port.js +232 -0
- package/src/lib/context-memory-kernel/live-compaction.js +375 -0
- package/src/lib/context-memory-kernel/memory-service.js +667 -0
- package/src/lib/context-memory-kernel/message-adapter.js +451 -0
- package/src/lib/context-memory-kernel/privacy-purge-port.js +244 -0
- package/src/lib/context-memory-kernel/provider-context.js +192 -0
- package/src/lib/context-memory-kernel/runtime.js +61 -0
- package/src/lib/direct-model-usage.js +22 -18
- package/src/lib/graph-kernel/cutover-authority-resolver.js +4 -5
- package/src/lib/graph-kernel/cutover-ledger.js +2 -2
- package/src/lib/graph-kernel/event-store.js +3 -5
- package/src/lib/graph-kernel/migration-saga.js +2 -2
- package/src/lib/graph-kernel/production-cutover-evidence.js +8 -1
- package/src/lib/hierarchical-memory.js +22 -0
- package/src/lib/hook-audit-store.js +170 -0
- package/src/lib/hook-event-bus.cjs +0 -0
- package/src/lib/hook-manager.js +25 -83
- package/src/lib/hook-runtime-adapters.js +233 -0
- package/src/lib/hook-runtime-contract.cjs +291 -0
- package/src/lib/hook-runtime-contract.js +21 -0
- package/src/lib/hook-trust.js +210 -0
- package/src/lib/hooks-v2-producers.js +52 -6
- package/src/lib/hooks-v2-runtime.js +443 -178
- package/src/lib/llm-pricing.js +10 -0
- package/src/lib/memory-manager.js +29 -4
- package/src/lib/model-context-window.js +52 -0
- package/src/lib/permanent-memory.js +17 -0
- package/src/lib/plan-mode.js +8 -4
- package/src/lib/process-execution-broker/index.js +208 -16
- package/src/lib/runtime-usage-ledger.js +20 -0
- package/src/lib/session-consolidator.js +59 -0
- package/src/lib/session-core-singletons.js +17 -4
- package/src/lib/session-hooks.js +29 -2
- package/src/lib/session-transcript-migration.js +188 -0
- package/src/lib/settings-hook-events.cjs +37 -401
- package/src/lib/settings-hook-events.js +210 -8
- package/src/lib/settings-hooks.cjs +42 -83
- package/src/lib/ws-session-manager.js +4 -1
- package/src/repl/agent-repl.js +186 -53
- package/src/runtime/agent-core.js +376 -350
- package/src/runtime/headless-runner.js +40 -33
- package/src/runtime/headless-stream.js +131 -30
- package/src/assets/web-panel/assets/MarkdownRenderer-Dt8hH_qA.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-Jg2dVUc-.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-0jYeVmyY.js +0 -7
- package/src/assets/web-panel/assets/devWarning-BX944k9E.js +0 -1
- package/src/assets/web-panel/assets/index-C_9Jt_SR.js +0 -1
- 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
|
-
|
|
54
|
-
|
|
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 =
|
|
116
|
-
|
|
117
|
-
|
|
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 (
|
|
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:
|
|
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
|
|
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(
|
|
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
|
-
|
|
576
|
-
|
|
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
|
|
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
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
755
|
+
authorityRoot,
|
|
697
756
|
);
|
|
698
757
|
let result;
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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
|
|
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]
|
|
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
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
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
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
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
|
-
|
|
1093
|
+
child.kill?.();
|
|
888
1094
|
} catch {
|
|
889
|
-
|
|
1095
|
+
// The timeout decision is already fail-closed.
|
|
890
1096
|
}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
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")
|
|
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
|
|