chainlesschain 0.162.171 → 0.162.173
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/bin/chainlesschain.js +8 -0
- package/package.json +6 -1
- package/src/assets/web-panel/assets/{AIOps-Cf8yb18C.js → AIOps-228_yS1K.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CsFT6FMt.js → ActionButton-qEKhWWk_.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DQe_hXMA.js → Analytics-Drma7Pkx.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-Cl9Pp7jk.js → AppLayout-B-gexhEy.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-CBEzLVjF.js → Artifacts-CWNSO5Af.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-BS2Un14C.js → Audit-DY3j9Ltk.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DmcDH8OK.js → BackgroundAgents-Bz2pcyfS.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DvzjTkkn.js → Backup-Bg5aAkWT.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DnbpuyME.js → BaseInput-35_LrETp.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BJRRCm2P.js → Chat-CWvrCIuL.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-Bgpxyqd_.js → ChatBubbleRenderer-7iAWWEmJ.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DFOrWmvm.js → Checkbox-B4qv17qH.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BIQ2kU6Q.js → Codegen-Co18wV4m.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BI3R-N4I.js → Col-D0RjQKek.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CPiAwsHT.js → Community-C5r80sMV.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Bv4cslSK.js → Compact-Cnwsi_GQ.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CSlDG1NZ.js → Compliance-EFmGHrSw.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CFcRumXf.js → Cowork-7Ix8iqrx.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-DKYKMmOn.js → Cron-Dpvn36K7.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CxPMByp2.js → Crosschain-DgwE0dXI.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DJqiYw1C.js → DID-79Xkd_Iy.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-D2dLMxqy.js → Dashboard-BX-kQk9j.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BnJc384A.js → Dropdown-B2hIzIKh.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DctFvm-J.js → EmailListRenderer-CwgczLkA.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-J_xg-Qop.js → FamilyGuardDashboard-D1dpW73p.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BckL07Uw.js → Federation-CzGSfm-g.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-B8s5ccSO.js → FormItemContext-Bdgq6Ctr.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-hPMBK2No.js → GenericCardRenderer-BdiR-AVP.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CfMpYLo7.js → Git-CyFl_ZOb.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-Bt3OcrC7.js → Governance-CIJfns5M.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BlKc336N.js → Inference-U2SL3a9n.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-B6p7og2c.js → KnowledgeGraph-B8TRMic6.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-uv5Re6YR.js → Logs-CN3aA59h.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-CId5JWjX.js → MarkdownRenderer-Op_hHJCw.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-CfNbntHt.js → Marketplace-CIu4bo4n.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-YRXt74Yk.js → McpTools-dMtEtLGH.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-BfQQs-Jd.js → Memory-B2a1qG3t.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BuGC-4S2.js → MobileBridge-Ci8N7KT8.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Czieqw1m.js → MobileProjects-CnJwGBSZ.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-DHWHOVCO.js → Mtc-B_yNoA93.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-DWSEbMW-.js → MtcAudit-DLYUJLFa.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DrhEv1vq.js → Multisig-lGvE7Ulk.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-D8wKCu7E.js → NLProgramming-CSl7-p0U.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-AFeqq0I_.js → Notes-CRzhFyCr.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-D6qenYUo.js → NotificationSettings-D8vIFpxx.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-CF0X4fyB.js → OrderTableRenderer-BK07A_0l.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DdfbNqf6.js → Organization-5CnL1jaB.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BFEvnLfd.js → Overflow-B4QvQ7ku.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DtL6wbkz.js → P2P-IwrXGm2f.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CQPriTud.js → PdhVaultBrowser-CNr6Ocb1.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-Bj25ANmR.js → Permissions-CcKqZlw6.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BNj2Oux0.js → PersonalDataHub-zUZb-JzZ.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-Dyj8UEaG.js → Pipeline-mMelf_X6.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BBUrWRFz.js → Privacy-BaA6CMRz.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BdX7uQn6.js → ProjectInit-CI8OJYpf.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BghtW1zr.js → ProjectSettings-C6ZJ3hQv.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-9VTquZCz.js → Projects-BVljPeGz.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-D-0K_Eza.js → Providers-D_bjZHj6.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DDaN30_D.js → QrScannerModal-TgVSKKTP.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DfHlifOj.js → QuickAsk-CnnleZmK.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CgLIvYE2.js → Recommend-zqJL4tgH.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DpOPrwE4.js → RemoteSession-bBFpdwjH.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-wMexlbCE.js → Reputation-tKkKrza-.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D9aD_nrc.js → Row-DgQaPsSR.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Drg7l6zu.js → RssFeed-B3PJQQ_a.js} +3 -3
- package/src/assets/web-panel/assets/{Search-BKO1nrwN.js → Search-BX2HUESa.js} +1 -1
- package/src/assets/web-panel/assets/{Security-G3Kyk-TG.js → Security-aqzxU6JQ.js} +4 -4
- package/src/assets/web-panel/assets/{Services-xJReTOna.js → Services-BiehYAcJ.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-ldVDVTPG.js → Skeleton-aWL9v64n.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-emwNYV0j.js → Skills-DnLhre12.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CZYwGp1K.js → Sla-C2ZJiegm.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-BpQdJ1IM.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-Dcs5Hhj6.js → SyncSettings-C93cxqz8.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CerFAo26.js → Tasks-oSM3jeQI.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BdZUbOg3.js → Templates-DJa_a4iY.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-B4c_7Bsw.js → Tenant-B7p9ktEF.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-aljUKGFD.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-tc6sp0TV.js → TimelineRenderer-CDcFH-4e.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Bw_vkMJq.js → Tokens-KISm6N5n.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DkbJnpvz.js → Trigger-BBYrtPba.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CIf9kqN1.js → Trust-BMuSnufM.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-Cjwllnea.js → UkeySign-BqZbe27i.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-kgfkY7AN.js → VideoEditing-C-21zOQc.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CYWXt4u9.js → Wallet-EwfAuGhE.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-QgPwyWCO.js → WebAuthn-D__2NkcW.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-Co0lha18.js → WorkflowEditor-C7pTi9eg.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CI__Ox-r.js → chat-DGJsLa46.js} +1 -1
- package/src/assets/web-panel/assets/{colors-Csv0eYYd.js → colors-DLGee_hV.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-e53UCdHy.js → compact-item-uXOmbkDm.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BMPGRDhV.js → createContext-CsPfwwcp.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CVrl2cXX.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-Dz68qIAq.js → hasIn-BHvO8oYD.js} +1 -1
- package/src/assets/web-panel/assets/{index-CoGSbW5J.js → index-477LYji2.js} +1 -1
- package/src/assets/web-panel/assets/{index-53ljQo_x.js → index-9F6z582g.js} +1 -1
- package/src/assets/web-panel/assets/{index-ajz0E0pK.js → index-B2NtdqkO.js} +1 -1
- package/src/assets/web-panel/assets/{index-ygfUVVFv.js → index-B5XTuu1V.js} +1 -1
- package/src/assets/web-panel/assets/{index-wcCouSNq.js → index-BDV08Lfd.js} +1 -1
- package/src/assets/web-panel/assets/{index-t6f80uqL.js → index-BOXMd0h7.js} +17 -17
- package/src/assets/web-panel/assets/index-BPY_5R3R.js +1 -0
- package/src/assets/web-panel/assets/{index-BHkDRNA5.js → index-BZa2DYkz.js} +1 -1
- package/src/assets/web-panel/assets/{index-DCl4Xb4N.js → index-Bb8I5bJW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BW2dImbg.js → index-By5mviLz.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dk9Gi8xd.js → index-C0mLGe0N.js} +1 -1
- package/src/assets/web-panel/assets/{index-CXuON9OG.js → index-C0w_0UmL.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1s1B8Iy.js → index-CN3eksmo.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJqQL6so.js → index-CNCYZBCQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BT3CV4Nh.js → index-Cc_spyrU.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNtZMPKV.js → index-CeJ_IuUy.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-NbcAbP.js → index-Ckb7veFD.js} +1 -1
- package/src/assets/web-panel/assets/{index-K5E_wjut.js → index-ClWReh9U.js} +1 -1
- package/src/assets/web-panel/assets/index-CrRvYSFJ.js +1 -0
- package/src/assets/web-panel/assets/{index-5iicv5af.js → index-D3UkkZps.js} +1 -1
- package/src/assets/web-panel/assets/{index-CrsoSnQF.js → index-D73CnO2G.js} +1 -1
- package/src/assets/web-panel/assets/{index-HbhQRrM2.js → index-D7EKxUXQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DyqcHiL1.js → index-DA8wNocR.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dtjm1K1a.js → index-DEOm1Go8.js} +1 -1
- package/src/assets/web-panel/assets/{index--7tHEadk.js → index-DHMZKkRS.js} +1 -1
- package/src/assets/web-panel/assets/{index-DnXm3Qsh.js → index-DJBB-U5Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-DVCd2aMX.js → index-DJkVI1UW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpTzqJ0Z.js → index-DbkMc0Qq.js} +1 -1
- package/src/assets/web-panel/assets/{index-DctYrVMq.js → index-DjX8mK9d.js} +1 -1
- package/src/assets/web-panel/assets/{index-_LmPlyeV.js → index-DmcFSHuA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CvLBQk_M.js → index-DqNI4xlm.js} +1 -1
- package/src/assets/web-panel/assets/{index-C53o-MdJ.js → index-DvQrYrw_.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOpz2qS1.js → index-Gqs5qts7.js} +1 -1
- package/src/assets/web-panel/assets/{index-N9DkR1i7.js → index-MAaNHDU0.js} +1 -1
- package/src/assets/web-panel/assets/{index-CYUxs6J1.js → index-YslvEzZs.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0bg74e1.js → index-gTQZVYbs.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5hLtxVz.js → index-hzI1TGEs.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4lKPJy1.js → index-vWJiZGAS.js} +1 -1
- package/src/assets/web-panel/assets/{index-CyLUaP2i.js → index-wnZd4y25.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DVCeQJrT.js → initDefaultProps-oK6Ac1vm.js} +1 -1
- package/src/assets/web-panel/assets/{motion-chBCR_8v.js → motion-BmETDqwI.js} +1 -1
- package/src/assets/web-panel/assets/{move-BziPlrh6.js → move-0DfUhr7q.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-Cenk0OD7.js → mtc-parser-BDG-x4aO.js} +1 -1
- package/src/assets/web-panel/assets/{omit-D-AiRsei.js → omit-C_n1NOjZ.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CHw3Fvqt.js → pickAttrs-j5w-2Trf.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-g8I-rCad.js → placementArrow-DDVTNAah.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BWffAr9k.js → responsiveObserve-DM30qzcv.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BmUQqhkb.js → slide-BcVzNlnc.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-B89gCO-y.js → statusUtils-VQh4j7zt.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-x1bnCF73.js → styleChecker-DcHl95y_.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-B-k1_Ggw.js → useFlexGapSupport-C5bRw-Km.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Bc3PUqQk.js → useFs-IcE9exrQ.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-D8qFDX1I.js → usePersonalDataHub-BGv3NgE4.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-UaL5-WGg.js → vnode-B9pXxNhd.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-Dv7bsbLs.js → zoom-Bba0fIkh.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/permissions.js +24 -1
- package/src/index.js +63 -845
- package/src/lib/agent-ipc-bus.js +331 -0
- package/src/lib/context-source-ledger.js +164 -0
- package/src/lib/execution-trace/index.js +10 -0
- package/src/lib/execution-trace/metrics-collector.js +218 -0
- package/src/lib/execution-trace/trace-context.js +182 -0
- package/src/lib/hook-manager.js +17 -0
- package/src/lib/hooks-v2-runtime.js +278 -0
- package/src/lib/metrics-collector.js +127 -0
- package/src/lib/observability/index.js +35 -0
- package/src/lib/observability/otlp-exporter.js +260 -0
- package/src/lib/otlp-exporter.js +45 -0
- package/src/lib/process-execution-broker/SPAWN_AUDIT_REPORT.md +117 -0
- package/src/lib/process-execution-broker/index.js +450 -0
- package/src/lib/process-execution-broker/patch-child-process.js +50 -0
- package/src/lib/process-execution-broker/spawn-audit.json +3814 -0
- package/src/lib/runtime-provenance-ledger.js +190 -0
- package/src/lib/session-hooks.js +41 -0
- package/src/program-base.js +4 -1
- package/src/repl/agent-repl.js +2 -0
- package/src/assets/web-panel/assets/SpeechSettings-CyaPrzOF.js +0 -1
- package/src/assets/web-panel/assets/Terminal-VVFLS3Ol.js +0 -3
- package/src/assets/web-panel/assets/devWarning-BIulobq7.js +0 -1
- package/src/assets/web-panel/assets/index-BGkiYnET.js +0 -1
- package/src/assets/web-panel/assets/index-Eyhrr_3u.js +0 -1
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MetricsCollector - M3 轻量级指标收集器
|
|
3
|
+
*
|
|
4
|
+
* 收集CLI运行时的性能指标:
|
|
5
|
+
* - 执行计数器
|
|
6
|
+
* - 延迟直方图
|
|
7
|
+
* - 标签化指标
|
|
8
|
+
* - 自动与traceId关联
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
12
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
13
|
+
import { traceContext } from "./trace-context.js";
|
|
14
|
+
|
|
15
|
+
class MetricsCollector {
|
|
16
|
+
constructor() {
|
|
17
|
+
this._counters = new Map();
|
|
18
|
+
this._histograms = new Map();
|
|
19
|
+
this._gauges = new Map();
|
|
20
|
+
this._flushedAt = Date.now();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Increment a counter metric
|
|
25
|
+
* @param {string} name - Metric name
|
|
26
|
+
* @param {number} value - Value to increment by (default: 1)
|
|
27
|
+
* @param {Object} labels - Key-value labels
|
|
28
|
+
*/
|
|
29
|
+
increment(name, value = 1, labels = {}) {
|
|
30
|
+
const key = this._metricKey(name, labels);
|
|
31
|
+
const current = this._counters.get(key) || 0;
|
|
32
|
+
this._counters.set(key, current + value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Record a value in a histogram (latency, sizes, etc.)
|
|
37
|
+
* @param {string} name - Metric name
|
|
38
|
+
* @param {number} value - Value to record
|
|
39
|
+
* @param {Object} labels - Key-value labels
|
|
40
|
+
*/
|
|
41
|
+
observe(name, value, labels = {}) {
|
|
42
|
+
const key = this._metricKey(name, labels);
|
|
43
|
+
const data = this._histograms.get(key) || {
|
|
44
|
+
count: 0,
|
|
45
|
+
sum: 0,
|
|
46
|
+
min: Infinity,
|
|
47
|
+
max: -Infinity,
|
|
48
|
+
values: [],
|
|
49
|
+
};
|
|
50
|
+
data.count += 1;
|
|
51
|
+
data.sum += value;
|
|
52
|
+
data.min = Math.min(data.min, value);
|
|
53
|
+
data.max = Math.max(data.max, value);
|
|
54
|
+
// Keep last 1000 values for percentile calculation
|
|
55
|
+
if (data.values.length > 1000) {
|
|
56
|
+
data.values.shift();
|
|
57
|
+
}
|
|
58
|
+
data.values.push(value);
|
|
59
|
+
this._histograms.set(key, data);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Set a gauge value
|
|
64
|
+
* @param {string} name - Metric name
|
|
65
|
+
* @param {number} value - Gauge value
|
|
66
|
+
* @param {Object} labels - Key-value labels
|
|
67
|
+
*/
|
|
68
|
+
setGauge(name, value, labels = {}) {
|
|
69
|
+
const key = this._metricKey(name, labels);
|
|
70
|
+
this._gauges.set(key, value);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Time an async function and record latency
|
|
75
|
+
* @param {string} name - Metric name
|
|
76
|
+
* @param {Function} fn - Async function to time
|
|
77
|
+
* @param {Object} labels - Key-value labels
|
|
78
|
+
* @returns {Promise<*>} Function result
|
|
79
|
+
*/
|
|
80
|
+
async time(name, fn, labels = {}) {
|
|
81
|
+
const start = performance.now();
|
|
82
|
+
try {
|
|
83
|
+
const result = await fn();
|
|
84
|
+
this.observe(name, performance.now() - start, {
|
|
85
|
+
...labels,
|
|
86
|
+
status: "success",
|
|
87
|
+
});
|
|
88
|
+
this.increment(`${name}_total`, 1, { ...labels, status: "success" });
|
|
89
|
+
return result;
|
|
90
|
+
} catch (err) {
|
|
91
|
+
this.observe(name, performance.now() - start, {
|
|
92
|
+
...labels,
|
|
93
|
+
status: "error",
|
|
94
|
+
});
|
|
95
|
+
this.increment(`${name}_total`, 1, {
|
|
96
|
+
...labels,
|
|
97
|
+
status: "error",
|
|
98
|
+
errorType: err.name,
|
|
99
|
+
});
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get all collected metrics as OTLP-compatible format
|
|
106
|
+
* @returns {Object} OTLP metrics data
|
|
107
|
+
*/
|
|
108
|
+
collect() {
|
|
109
|
+
const metrics = [];
|
|
110
|
+
|
|
111
|
+
// Convert counters
|
|
112
|
+
for (const [key, value] of this._counters) {
|
|
113
|
+
const { name, labels } = this._parseKey(key);
|
|
114
|
+
metrics.push({
|
|
115
|
+
name,
|
|
116
|
+
type: "counter",
|
|
117
|
+
value,
|
|
118
|
+
labels,
|
|
119
|
+
timestamp: Date.now(),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Convert histograms
|
|
124
|
+
for (const [key, data] of this._histograms) {
|
|
125
|
+
const { name, labels } = this._parseKey(key);
|
|
126
|
+
data.values.sort((a, b) => a - b);
|
|
127
|
+
const p50 = data.values[Math.floor(data.values.length * 0.5)];
|
|
128
|
+
const p95 = data.values[Math.floor(data.values.length * 0.95)];
|
|
129
|
+
const p99 = data.values[Math.floor(data.values.length * 0.99)];
|
|
130
|
+
metrics.push({
|
|
131
|
+
name,
|
|
132
|
+
type: "histogram",
|
|
133
|
+
count: data.count,
|
|
134
|
+
sum: data.sum,
|
|
135
|
+
min: data.min,
|
|
136
|
+
max: data.max,
|
|
137
|
+
avg: data.sum / data.count,
|
|
138
|
+
p50,
|
|
139
|
+
p95,
|
|
140
|
+
p99,
|
|
141
|
+
labels,
|
|
142
|
+
timestamp: Date.now(),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Convert gauges
|
|
147
|
+
for (const [key, value] of this._gauges) {
|
|
148
|
+
const { name, labels } = this._parseKey(key);
|
|
149
|
+
metrics.push({
|
|
150
|
+
name,
|
|
151
|
+
type: "gauge",
|
|
152
|
+
value,
|
|
153
|
+
labels,
|
|
154
|
+
timestamp: Date.now(),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
resourceMetrics: [
|
|
160
|
+
{
|
|
161
|
+
resource: {
|
|
162
|
+
attributes: [
|
|
163
|
+
{
|
|
164
|
+
key: "service.name",
|
|
165
|
+
value: { stringValue: "chainlesschain-cli" },
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
key: "service.version",
|
|
169
|
+
value: { stringValue: "0.10.0-alpha.74" },
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
scopeMetrics: [
|
|
174
|
+
{
|
|
175
|
+
scope: { name: "chainlesschain.cli" },
|
|
176
|
+
metrics,
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Reset all metrics
|
|
186
|
+
*/
|
|
187
|
+
reset() {
|
|
188
|
+
this._counters.clear();
|
|
189
|
+
this._histograms.clear();
|
|
190
|
+
this._gauges.clear();
|
|
191
|
+
this._flushedAt = Date.now();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
_metricKey(name, labels) {
|
|
195
|
+
const labelStr = Object.entries(labels)
|
|
196
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
197
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
198
|
+
.join(",");
|
|
199
|
+
return labelStr ? `${name}{${labelStr}}` : name;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
_parseKey(key) {
|
|
203
|
+
const match = key.match(/^([^{]+)(?:\{([^}]+)\})?$/);
|
|
204
|
+
if (!match) return { name: key, labels: {} };
|
|
205
|
+
const [, name, labelStr] = match;
|
|
206
|
+
if (!labelStr) return { name, labels: {} };
|
|
207
|
+
const labels = {};
|
|
208
|
+
for (const pair of labelStr.split(",")) {
|
|
209
|
+
const [k, v] = pair.split("=");
|
|
210
|
+
if (k) labels[k] = v;
|
|
211
|
+
}
|
|
212
|
+
return { name, labels };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export { MetricsCollector };
|
|
217
|
+
export const metricsCollector = new MetricsCollector();
|
|
218
|
+
export default metricsCollector;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TraceContext - W3C Trace Context 兼容的分布式追踪上下文
|
|
3
|
+
*
|
|
4
|
+
* 支持:
|
|
5
|
+
* - traceparent 头格式: {version}-{traceId}-{parentId}-{traceFlags}
|
|
6
|
+
* - tracestate 头传播
|
|
7
|
+
* - AsyncLocalStorage 上下文传播
|
|
8
|
+
* - Span 创建与管理
|
|
9
|
+
* - Baggage 透传
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
13
|
+
import { randomBytes } from "node:crypto";
|
|
14
|
+
|
|
15
|
+
class TraceContext {
|
|
16
|
+
constructor() {
|
|
17
|
+
this._asyncLocalStorage = new AsyncLocalStorage();
|
|
18
|
+
this._spans = new Map();
|
|
19
|
+
this._listeners = new Set();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
generateTraceId() {
|
|
23
|
+
return randomBytes(16).toString("hex");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
generateSpanId() {
|
|
27
|
+
return randomBytes(8).toString("hex");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
parseTraceparent(traceparent) {
|
|
31
|
+
if (!traceparent) return null;
|
|
32
|
+
const parts = traceparent.split("-");
|
|
33
|
+
if (parts.length !== 4) return null;
|
|
34
|
+
const [version, traceId, parentId, traceFlags] = parts;
|
|
35
|
+
if (
|
|
36
|
+
version.length !== 2 ||
|
|
37
|
+
traceId.length !== 32 ||
|
|
38
|
+
parentId.length !== 16 ||
|
|
39
|
+
traceFlags.length !== 2
|
|
40
|
+
) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return { version, traceId, parentId, traceFlags: parseInt(traceFlags, 16) };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
formatTraceparent(traceId, spanId, traceFlags = 0x01) {
|
|
47
|
+
return `00-${traceId}-${spanId}-${traceFlags.toString(16).padStart(2, "0")}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getCurrentContext() {
|
|
51
|
+
return this._asyncLocalStorage.getStore();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
startRootSpan(name, attributes = {}) {
|
|
55
|
+
const traceId = this.generateTraceId();
|
|
56
|
+
const spanId = this.generateSpanId();
|
|
57
|
+
const context = {
|
|
58
|
+
traceId,
|
|
59
|
+
spanId,
|
|
60
|
+
parentSpanId: null,
|
|
61
|
+
name,
|
|
62
|
+
attributes,
|
|
63
|
+
startTime: Date.now(),
|
|
64
|
+
tracestate: "",
|
|
65
|
+
baggage: {},
|
|
66
|
+
};
|
|
67
|
+
this._spans.set(spanId, context);
|
|
68
|
+
this._emit("span:start", context);
|
|
69
|
+
return context;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
startSpan(name, attributes = {}) {
|
|
73
|
+
const parent = this.getCurrentContext();
|
|
74
|
+
if (!parent) {
|
|
75
|
+
return this.startRootSpan(name, attributes);
|
|
76
|
+
}
|
|
77
|
+
const spanId = this.generateSpanId();
|
|
78
|
+
const context = {
|
|
79
|
+
traceId: parent.traceId,
|
|
80
|
+
spanId,
|
|
81
|
+
parentSpanId: parent.spanId,
|
|
82
|
+
name,
|
|
83
|
+
attributes: { ...attributes },
|
|
84
|
+
startTime: Date.now(),
|
|
85
|
+
tracestate: parent.tracestate || "",
|
|
86
|
+
baggage: { ...(parent.baggage || {}) },
|
|
87
|
+
};
|
|
88
|
+
this._spans.set(spanId, context);
|
|
89
|
+
this._emit("span:start", context);
|
|
90
|
+
return context;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
endSpan(span, status = { code: "OK" }) {
|
|
94
|
+
if (!span || !this._spans.has(span.spanId)) return;
|
|
95
|
+
span.endTime = Date.now();
|
|
96
|
+
span.duration = span.endTime - span.startTime;
|
|
97
|
+
span.status = status;
|
|
98
|
+
this._emit("span:end", span);
|
|
99
|
+
this._spans.delete(span.spanId);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async runInContext(context, fn) {
|
|
103
|
+
return this._asyncLocalStorage.run(context, fn);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getPropagationEnv() {
|
|
107
|
+
const ctx = this.getCurrentContext();
|
|
108
|
+
if (!ctx) return {};
|
|
109
|
+
const env = {
|
|
110
|
+
TRACEPARENT: this.formatTraceparent(ctx.traceId, ctx.spanId),
|
|
111
|
+
};
|
|
112
|
+
if (ctx.tracestate) {
|
|
113
|
+
env.TRACESTATE = ctx.tracestate;
|
|
114
|
+
}
|
|
115
|
+
if (ctx.baggage && Object.keys(ctx.baggage).length > 0) {
|
|
116
|
+
env.BAGGAGE = Object.entries(ctx.baggage)
|
|
117
|
+
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
|
|
118
|
+
.join(",");
|
|
119
|
+
}
|
|
120
|
+
return env;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
extractFromEnv(env = process.env) {
|
|
124
|
+
const traceparent = env.TRACEPARENT || env.traceparent;
|
|
125
|
+
if (!traceparent) return null;
|
|
126
|
+
const parsed = this.parseTraceparent(traceparent);
|
|
127
|
+
if (!parsed) return null;
|
|
128
|
+
const context = {
|
|
129
|
+
traceId: parsed.traceId,
|
|
130
|
+
spanId: parsed.parentId,
|
|
131
|
+
parentSpanId: null,
|
|
132
|
+
name: "inherited",
|
|
133
|
+
attributes: {},
|
|
134
|
+
startTime: Date.now(),
|
|
135
|
+
tracestate: env.TRACESTATE || env.tracestate || "",
|
|
136
|
+
baggage: this._parseBaggage(env.BAGGAGE || env.baggage),
|
|
137
|
+
};
|
|
138
|
+
return context;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
on(event, listener) {
|
|
142
|
+
this._listeners.add({ event, listener });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
off(event, listener) {
|
|
146
|
+
for (const entry of this._listeners) {
|
|
147
|
+
if (entry.event === event && entry.listener === listener) {
|
|
148
|
+
this._listeners.delete(entry);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
_emit(event, data) {
|
|
155
|
+
for (const { event: ev, listener } of this._listeners) {
|
|
156
|
+
if (ev === event) {
|
|
157
|
+
try {
|
|
158
|
+
listener(data);
|
|
159
|
+
} catch (e) {
|
|
160
|
+
// Silently ignore listener errors
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
_parseBaggage(baggage) {
|
|
167
|
+
if (!baggage) return {};
|
|
168
|
+
const result = {};
|
|
169
|
+
for (const pair of baggage.split(",")) {
|
|
170
|
+
const eqIdx = pair.indexOf("=");
|
|
171
|
+
if (eqIdx === -1) continue;
|
|
172
|
+
const key = decodeURIComponent(pair.slice(0, eqIdx).trim());
|
|
173
|
+
const value = decodeURIComponent(pair.slice(eqIdx + 1).trim());
|
|
174
|
+
result[key] = value;
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export { TraceContext };
|
|
181
|
+
export const traceContext = new TraceContext();
|
|
182
|
+
export default traceContext;
|
package/src/lib/hook-manager.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import crypto from "crypto";
|
|
7
7
|
import { execSync } from "child_process";
|
|
8
|
+
import broker from "./process-execution-broker/index.js";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Hook priority levels — lower values run first.
|
|
@@ -62,6 +63,13 @@ export const HookEvents = {
|
|
|
62
63
|
CIFailure: "CIFailure",
|
|
63
64
|
IterationWarning: "IterationWarning",
|
|
64
65
|
IterationBudgetExhausted: "IterationBudgetExhausted",
|
|
66
|
+
/**
|
|
67
|
+
* Stop: Fired at the END of every agent turn/loop iteration, BEFORE waiting
|
|
68
|
+
* for the next user input. Aligns with Claude Code hook spec.
|
|
69
|
+
* Fires regardless of outcome (success, error, max-iterations, stop).
|
|
70
|
+
* Does NOT fire on final process exit (use SessionEnd/Shutdown for that).
|
|
71
|
+
*/
|
|
72
|
+
Stop: "Stop",
|
|
65
73
|
};
|
|
66
74
|
|
|
67
75
|
/**
|
|
@@ -282,6 +290,15 @@ export async function executeHook(hook, context = {}) {
|
|
|
282
290
|
HOOK_EVENT: hook.event,
|
|
283
291
|
HOOK_CONTEXT: JSON.stringify(context),
|
|
284
292
|
};
|
|
293
|
+
// M1: Record hook command execution in broker audit log
|
|
294
|
+
broker.auditLog.push({
|
|
295
|
+
pid: process.pid,
|
|
296
|
+
origin: "hook",
|
|
297
|
+
command: cmd,
|
|
298
|
+
args: [],
|
|
299
|
+
startedAt: Date.now(),
|
|
300
|
+
sessionId: context?.sessionId,
|
|
301
|
+
});
|
|
285
302
|
const output = execSync(cmd, {
|
|
286
303
|
encoding: "utf-8",
|
|
287
304
|
timeout: hook.timeout || 5000,
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hooks v2 System — M3-1: 完整18事件schema + 5种executor类型
|
|
3
|
+
* 对应文档 §2.3
|
|
4
|
+
*
|
|
5
|
+
* 支持的18种生命周期事件:
|
|
6
|
+
* Session: PreToolUse, PostToolUse, Notification, Stop, SubagentStop
|
|
7
|
+
* Auth: PreCommit, PostCommit
|
|
8
|
+
* Skill: UserPromptSubmit, SessionStart, SessionEnd
|
|
9
|
+
* Model: PreCompact, ModelSelection
|
|
10
|
+
* Config: ConfigChange, PermissionAllow, PermissionDeny
|
|
11
|
+
* Timeline: TimelineEntry
|
|
12
|
+
* MCP: McpRequest, McpResponse
|
|
13
|
+
*
|
|
14
|
+
* 支持的executor类型:
|
|
15
|
+
* 1. command (shell command, current hooks.json only supports this)
|
|
16
|
+
* 2. http (webhook)
|
|
17
|
+
* 3. prompt (inline prompt template)
|
|
18
|
+
* 4. agent (dispatch to cc agent/skill)
|
|
19
|
+
* 5. js (inline JS function via vm sandbox)
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { EventEmitter } from "node:events";
|
|
23
|
+
import crypto from "node:crypto";
|
|
24
|
+
import fs from "node:fs/promises";
|
|
25
|
+
import path from "node:path";
|
|
26
|
+
import broker from "./process-execution-broker/index.js";
|
|
27
|
+
|
|
28
|
+
const VALID_HOOK_EVENTS = new Set([
|
|
29
|
+
// Session
|
|
30
|
+
"PreToolUse", "PostToolUse", "Notification", "Stop", "SubagentStop",
|
|
31
|
+
// Auth
|
|
32
|
+
"PreCommit", "PostCommit",
|
|
33
|
+
// Skill
|
|
34
|
+
"UserPromptSubmit", "SessionStart", "SessionEnd",
|
|
35
|
+
// Model
|
|
36
|
+
"PreCompact", "ModelSelection",
|
|
37
|
+
// Config
|
|
38
|
+
"ConfigChange", "PermissionAllow", "PermissionDeny",
|
|
39
|
+
// Timeline
|
|
40
|
+
"TimelineEntry",
|
|
41
|
+
// MCP
|
|
42
|
+
"McpRequest", "McpResponse",
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
const VALID_EXECUTOR_TYPES = new Set(["command", "http", "prompt", "agent", "js"]);
|
|
46
|
+
|
|
47
|
+
class HooksV2Runtime extends EventEmitter {
|
|
48
|
+
constructor(configDir) {
|
|
49
|
+
super();
|
|
50
|
+
this.configDir = configDir;
|
|
51
|
+
this.hooks = new Map(); // eventName -> HookDefinition[]
|
|
52
|
+
this.executionLog = [];
|
|
53
|
+
this._loaded = false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @typedef {Object} HookDefinition
|
|
58
|
+
* @property {string} id
|
|
59
|
+
* @property {string} event
|
|
60
|
+
* @property {'command'|'http'|'prompt'|'agent'|'js'} type
|
|
61
|
+
* @property {string} [command] - for type=command
|
|
62
|
+
* @property {string[]} [args]
|
|
63
|
+
* @property {string} [url] - for type=http
|
|
64
|
+
* @property {string} [method] - GET/POST
|
|
65
|
+
* @property {Object} [headers]
|
|
66
|
+
* @property {string} [template] - for type=prompt
|
|
67
|
+
* @property {string} [agentName] - for type=agent
|
|
68
|
+
* @property {string} [skillName] - for type=agent
|
|
69
|
+
* @property {string} [code] - for type=js
|
|
70
|
+
* @property {number} [timeoutMs]
|
|
71
|
+
* @property {boolean} [blocking] - default false
|
|
72
|
+
* @property {string[]} [if] - conditional matchers
|
|
73
|
+
* @property {string} [description]
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
async loadHooks(hooksPath) {
|
|
77
|
+
try {
|
|
78
|
+
const content = await fs.readFile(hooksPath, "utf8");
|
|
79
|
+
const config = JSON.parse(content);
|
|
80
|
+
this.hooks.clear();
|
|
81
|
+
|
|
82
|
+
const hookDefs = Array.isArray(config) ? config : (config.hooks || []);
|
|
83
|
+
for (const def of hookDefs) {
|
|
84
|
+
if (!VALID_HOOK_EVENTS.has(def.event)) {
|
|
85
|
+
process.emitWarning(`[hooks-v2] Unknown event type: ${def.event}, skipping`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (!VALID_EXECUTOR_TYPES.has(def.type)) {
|
|
89
|
+
process.emitWarning(`[hooks-v2] Unknown executor type: ${def.type} for ${def.event}, skipping`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const id = def.id || crypto.randomUUID();
|
|
93
|
+
if (!this.hooks.has(def.event)) this.hooks.set(def.event, []);
|
|
94
|
+
this.hooks.get(def.event).push({ id, ...def });
|
|
95
|
+
}
|
|
96
|
+
this._loaded = true;
|
|
97
|
+
this.emit("loaded", { count: hookDefs.length, events: Array.from(this.hooks.keys()) });
|
|
98
|
+
} catch (e) {
|
|
99
|
+
if (e.code !== "ENOENT") {
|
|
100
|
+
this.emit("load:error", e);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Register a hook programmatically
|
|
107
|
+
*/
|
|
108
|
+
registerHook(def) {
|
|
109
|
+
if (!VALID_HOOK_EVENTS.has(def.event)) throw new Error(`Invalid event: ${def.event}`);
|
|
110
|
+
if (!VALID_EXECUTOR_TYPES.has(def.type)) throw new Error(`Invalid executor type: ${def.type}`);
|
|
111
|
+
const id = def.id || crypto.randomUUID();
|
|
112
|
+
const full = { id, blocking: false, timeoutMs: 30000, ...def };
|
|
113
|
+
if (!this.hooks.has(def.event)) this.hooks.set(def.event, []);
|
|
114
|
+
this.hooks.get(def.event).push(full);
|
|
115
|
+
return id;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
unregisterHook(id) {
|
|
119
|
+
for (const [event, hooks] of this.hooks.entries()) {
|
|
120
|
+
const idx = hooks.findIndex(h => h.id === id);
|
|
121
|
+
if (idx >= 0) {
|
|
122
|
+
hooks.splice(idx, 1);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Fire an event - executes all registered hooks
|
|
131
|
+
* @param {string} eventName
|
|
132
|
+
* @param {Object} context - event context (tool_name, tool_input, session_id, etc.)
|
|
133
|
+
* @returns {Promise<{results: Array, blocked: boolean, blockingResult?: any}>}
|
|
134
|
+
*/
|
|
135
|
+
async emitEvent(eventName, context = {}) {
|
|
136
|
+
if (!VALID_HOOK_EVENTS.has(eventName)) {
|
|
137
|
+
throw new Error(`Invalid hook event: ${eventName}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const hooks = this.hooks.get(eventName) || [];
|
|
141
|
+
const results = [];
|
|
142
|
+
let blocked = false;
|
|
143
|
+
let blockingResult = null;
|
|
144
|
+
|
|
145
|
+
for (const hook of hooks) {
|
|
146
|
+
const execId = crypto.randomUUID();
|
|
147
|
+
const start = Date.now();
|
|
148
|
+
const record = { execId, hookId: hook.id, event: eventName, type: hook.type, startedAt: new Date() };
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
let result;
|
|
152
|
+
switch (hook.type) {
|
|
153
|
+
case "command":
|
|
154
|
+
result = await this._execCommand(hook, context);
|
|
155
|
+
break;
|
|
156
|
+
case "http":
|
|
157
|
+
result = await this._execHttp(hook, context);
|
|
158
|
+
break;
|
|
159
|
+
case "prompt":
|
|
160
|
+
result = await this._execPrompt(hook, context);
|
|
161
|
+
break;
|
|
162
|
+
case "agent":
|
|
163
|
+
result = await this._execAgent(hook, context);
|
|
164
|
+
break;
|
|
165
|
+
case "js":
|
|
166
|
+
result = await this._execJs(hook, context);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
record.durationMs = Date.now() - start;
|
|
171
|
+
record.status = "success";
|
|
172
|
+
record.result = result;
|
|
173
|
+
results.push(record);
|
|
174
|
+
|
|
175
|
+
// Check for blocking decision
|
|
176
|
+
if (hook.blocking && result && result.decision === "block") {
|
|
177
|
+
blocked = true;
|
|
178
|
+
blockingResult = result;
|
|
179
|
+
record.blocked = true;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.emit("hook:success", record);
|
|
183
|
+
} catch (err) {
|
|
184
|
+
record.durationMs = Date.now() - start;
|
|
185
|
+
record.status = "error";
|
|
186
|
+
record.error = err.message;
|
|
187
|
+
results.push(record);
|
|
188
|
+
this.emit("hook:error", record);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
this.executionLog.push(record);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { results, blocked, blockingResult };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async _execCommand(hook, context) {
|
|
198
|
+
const child = await broker.spawn(
|
|
199
|
+
hook.command, hook.args || [],
|
|
200
|
+
{
|
|
201
|
+
env: { ...process.env, CC_HOOK_EVENT: hook.event, CC_HOOK_CONTEXT: JSON.stringify(context) },
|
|
202
|
+
stdio: "pipe",
|
|
203
|
+
timeout: hook.timeoutMs,
|
|
204
|
+
},
|
|
205
|
+
{ origin: "hook", hookName: hook.id, timeout: hook.timeoutMs },
|
|
206
|
+
);
|
|
207
|
+
return new Promise((resolve, reject) => {
|
|
208
|
+
let stdout = "", stderr = "";
|
|
209
|
+
child.stdout.on("data", d => stdout += d);
|
|
210
|
+
child.stderr.on("data", d => stderr += d);
|
|
211
|
+
child.on("exit", (code) => {
|
|
212
|
+
if (code === 0) {
|
|
213
|
+
let parsed = {};
|
|
214
|
+
try { parsed = JSON.parse(stdout); } catch { parsed = { raw: stdout }; }
|
|
215
|
+
resolve({ ...parsed, exitCode: code, stderr });
|
|
216
|
+
} else {
|
|
217
|
+
reject(new Error(`Hook command failed with exit ${code}: ${stderr}`));
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async _execHttp(hook, context) {
|
|
224
|
+
// Simple HTTP webhook executor
|
|
225
|
+
const method = hook.method || "POST";
|
|
226
|
+
const controller = new AbortController();
|
|
227
|
+
const timer = setTimeout(() => controller.abort(), hook.timeoutMs || 30000);
|
|
228
|
+
try {
|
|
229
|
+
const res = await fetch(hook.url, {
|
|
230
|
+
method,
|
|
231
|
+
headers: { "Content-Type": "application/json", ...(hook.headers || {}) },
|
|
232
|
+
body: method !== "GET" ? JSON.stringify({ event: hook.event, context }) : undefined,
|
|
233
|
+
signal: controller.signal,
|
|
234
|
+
});
|
|
235
|
+
clearTimeout(timer);
|
|
236
|
+
return { status: res.status, body: await res.text().catch(() => "") };
|
|
237
|
+
} finally {
|
|
238
|
+
clearTimeout(timer);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async _execPrompt(hook, context) {
|
|
243
|
+
// Prompt template execution - returns expanded prompt for agent prepend
|
|
244
|
+
const vars = { ...context };
|
|
245
|
+
let expanded = hook.template || "";
|
|
246
|
+
expanded = expanded.replace(/\$\{(\w+)\}/g, (_, k) => vars[k] ?? "");
|
|
247
|
+
return { decision: "augment", prompt: expanded };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async _execAgent(hook, context) {
|
|
251
|
+
// Dispatch to agent/skill - stub for full integration
|
|
252
|
+
return { decision: "delegate", agentName: hook.agentName, skillName: hook.skillName, context };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async _execJs(hook, context) {
|
|
256
|
+
// In-process JS execution via vm (sandboxed) - stub for full vm2/isolated-vm integration
|
|
257
|
+
return { decision: "noop", note: "JS executor stub - context received", contextKeys: Object.keys(context) };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Get all registered hooks */
|
|
261
|
+
getHookRegistry() {
|
|
262
|
+
const registry = {};
|
|
263
|
+
for (const [event, hooks] of this.hooks.entries()) {
|
|
264
|
+
registry[event] = hooks.map(h => ({ id: h.id, type: h.type, description: h.description, blocking: h.blocking }));
|
|
265
|
+
}
|
|
266
|
+
return registry;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Get recent execution log */
|
|
270
|
+
getExecutionLog(limit = 50) {
|
|
271
|
+
return this.executionLog.slice(-limit);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Singleton instance
|
|
276
|
+
const hooksRuntime = new HooksV2Runtime();
|
|
277
|
+
export default hooksRuntime;
|
|
278
|
+
export { HooksV2Runtime, VALID_HOOK_EVENTS, VALID_EXECUTOR_TYPES };
|