chainlesschain 0.162.170 → 0.162.172
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-BTXN24I3.js → AIOps-oKCWU-LK.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BBB1VF4j.js → ActionButton-4CBfc5E4.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BVCXq0k6.js → Analytics-BEmR-k_b.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-C3EOo2s5.js → AppLayout-D6a4ZnrC.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CsJTZSMv.js → Artifacts-BRWIHCPl.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CP0AllJ_.js → Audit-CzGKlkTc.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BCAjs08m.js → BackgroundAgents-CmMYN10o.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BxQI5XH4.js → Backup-CJ6e0ukD.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Bu-PNzC5.js → BaseInput-BpEvDrNt.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DjPQynup.js → Chat-wWuwwjKu.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CygkRopu.js → ChatBubbleRenderer-CJLyGo_u.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BxL6xm7Z.js → Checkbox-C6pW60Br.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CcyB7h_6.js → Codegen-DF-nP22Y.js} +1 -1
- package/src/assets/web-panel/assets/{Col-8NQiKJJx.js → Col-BuueEwZW.js} +1 -1
- package/src/assets/web-panel/assets/{Community-B31qy5OS.js → Community-DuE0Yzwr.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-C-5fAsZ4.js → Compact-BrHim8EB.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BTSccvF0.js → Compliance-DgAet8f_.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BXctU9IK.js → Cowork-B9wY05lx.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-CxVFX9NG.js → Cron-CvNBxfJX.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DEF3dq8M.js → Crosschain-BnZRENa0.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BmKcE9he.js → DID-DiqYLHTI.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CECBq_wG.js → Dashboard-t3SsRzZL.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-U-iuPB0n.js → Dropdown-BCjMLam9.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-D8BIlSPL.js → EmailListRenderer-D2v9-IHJ.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BzQGfUH-.js → FamilyGuardDashboard-_iQu35qa.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-4_WlOqLZ.js → Federation-Bkty8VqB.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-gtrW-DdF.js → FormItemContext-DdPW81BY.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BLcvJJB6.js → GenericCardRenderer-DrNYM4Zj.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Bk0janGD.js → Git-BjF-B2h0.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CcfyMlV8.js → Governance-DPhGdJ1v.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CBLrAf4M.js → Inference--PPksOVj.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-ClguhteZ.js → KnowledgeGraph-y5WdV_JV.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-Dp_oeKfJ.js → Logs-B6_x7zba.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-Bg8zSyF8.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BHk50smM.js → Marketplace-CHpfMu1A.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BAY3CV6J.js → McpTools-qqjtX3Cf.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-w0pG2AKx.js → Memory-hASSgxbw.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-C0VkDUE3.js → MobileBridge-Dwh7Bhcf.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BCvQ80I9.js → MobileProjects-D09voh7W.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-DSIl41Tt.js → Mtc-9G1dXeId.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-D1mzUlIr.js → MtcAudit-CWtYXj6f.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-CeJz4VSS.js → Multisig-bDI0EnDk.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-_O3aweu8.js → NLProgramming-CTanRaBP.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Dt7_pO86.js → Notes-BdQENJFv.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Dhg3Nktv.js → NotificationSettings-JvnQdStO.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-QsAPcTb2.js → OrderTableRenderer-BMGL9RPJ.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BuiaUPkv.js → Organization-sBZnIb2D.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-C3jb6knl.js → Overflow-CzOhNGE6.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BOMyrLkb.js → P2P-D_D2yqda.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-UvLStCgS.js → PdhVaultBrowser-D5lKd_Kx.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-B9cN0dJH.js → Permissions-L5s-wENg.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-DNjW5qro.js → PersonalDataHub-iI49x6cH.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-CFLyZ6Sh.js → Pipeline-DPHqyk09.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-D3w6vOxf.js → Privacy-B-ZL9360.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-cwcVsn8K.js → ProjectInit-f05YzsN2.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-aUoAroc-.js → ProjectSettings-DZLKS6ex.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-CrzQv7Zv.js → Projects-ButF3DyM.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-jZaPizKR.js → Providers-CFDSXnkR.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DNzmwcfg.js → QrScannerModal-1N6Sdxdm.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-m-5Gwt70.js → QuickAsk-tEqRwRSR.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CrrAjNxP.js → Recommend-D4ZVHIwz.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-BDCMjRFe.js → RemoteSession-Cqm32lhm.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CPCt1Jc8.js → Reputation-BnjVsU_a.js} +1 -1
- package/src/assets/web-panel/assets/{Row-C-RorxHs.js → Row-DzpHyTuz.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CnQs4TuY.js → RssFeed-Du5-ouwx.js} +3 -3
- package/src/assets/web-panel/assets/{Search-B1jkeFZY.js → Search-CYztyf2o.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CGKI3vvd.js → Security-C1hVxy8Z.js} +3 -3
- package/src/assets/web-panel/assets/{Services-DQjesYF8.js → Services-BscEbAZ2.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-C9Ipj0A6.js → Skeleton-B2sMLqKQ.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-D2j54i9O.js → Skills-CoHKax06.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-xhrVjTeb.js → Sla-Bpie5KO6.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C6KMOUCa.js → SpeechSettings-BX63ijn5.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CmIPU5wR.js → SyncSettings-Bf72so5A.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-B-T1J42r.js → Tasks-CVB2PZk4.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Aq669sjw.js → Templates-q0qyclJx.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BKWUWNXB.js → Tenant-CW-W-i0k.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DEeAvA8L.js → Terminal-D7QoPiXQ.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-C465ySdn.js → TimelineRenderer-BaRDAbyA.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BgVqF-bX.js → Tokens-D6U4PRW8.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BMNHmxwH.js → Trigger-ClBDWdLo.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-B_adk89L.js → Trust-C_M6rWPq.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-Biw38mog.js → UkeySign-bXHx8s4C.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-AC-eaw5k.js → VideoEditing-BcIt_ieI.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-8lQ5_t0X.js → Wallet-Dd-4jcLs.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-JDmH_-DF.js → WebAuthn-B4F6yxQK.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-DHQAdITG.js → WorkflowEditor-BEHkpfJq.js} +1 -1
- package/src/assets/web-panel/assets/{chat-C3qauUVv.js → chat-D7hhRpIO.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DLP70JGe.js → colors-AMRhrRzS.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DjsoqA5t.js → compact-item-BYpsOET5.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CfEdl1in.js → createContext-CoUkR4LQ.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BRylvxzY.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DWwHHlBG.js → hasIn-CJKJMKki.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dfka-vzJ.js → index-8pmZHnQU.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgQ92xdO.js → index-BBJFiZ9Y.js} +1 -1
- package/src/assets/web-panel/assets/{index-VtCz3A64.js → index-BP69Ul3S.js} +1 -1
- package/src/assets/web-panel/assets/{index-C8wrUFzY.js → index-BTFHYauX.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5VL0NYy.js → index-BUJt2ar4.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-rD2rhi.js → index-Bj30Aq6h.js} +1 -1
- package/src/assets/web-panel/assets/{index-gu8zBhtp.js → index-BlpuqGt5.js} +1 -1
- package/src/assets/web-panel/assets/index-Bzvxvzlk.js +1 -0
- package/src/assets/web-panel/assets/{index-CgruUvB3.js → index-C19xZKIP.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTEcPJGf.js → index-C6bg22vc.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4k6JGm_.js → index-CH6XEWdj.js} +1 -1
- package/src/assets/web-panel/assets/{index-C0xMzvk8.js → index-CK1Kknpl.js} +1 -1
- package/src/assets/web-panel/assets/{index-UEbYl2pu.js → index-CNyLbBGj.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dmt1HDWZ.js → index-COg__P6M.js} +19 -19
- package/src/assets/web-panel/assets/{index-ArZCi1rK.js → index-CRlJOWsf.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqPuyN4p.js → index-CScG8TAY.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4gCFHtB.js → index-CdL9cQIE.js} +1 -1
- package/src/assets/web-panel/assets/{index-C47EiQ7d.js → index-Ck35u_0v.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dn2rNMmv.js → index-CkLZQtsh.js} +1 -1
- package/src/assets/web-panel/assets/{index-T5emAyFT.js → index-DFoujkWD.js} +1 -1
- package/src/assets/web-panel/assets/{index-DbFnfyKh.js → index-DFwh0R9h.js} +1 -1
- package/src/assets/web-panel/assets/{index-CsEk9s04.js → index-DStN9E7o.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTDpF8MS.js → index-DcozYrXV.js} +1 -1
- package/src/assets/web-panel/assets/{index-_g5q2hF3.js → index-De5mYp86.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bp_WNm2_.js → index-DfEVOzNb.js} +1 -1
- package/src/assets/web-panel/assets/{index-OfMwkFIo.js → index-Dfna8sKa.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcDeMCUK.js → index-DjbMaBLf.js} +1 -1
- package/src/assets/web-panel/assets/index-Dl6H3dxS.js +1 -0
- package/src/assets/web-panel/assets/{index-D-te-S9f.js → index-Dpl_SJuk.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKsWujXP.js → index-DyF9uYzB.js} +1 -1
- package/src/assets/web-panel/assets/{index-BydBh7oT.js → index-HF3GvATm.js} +1 -1
- package/src/assets/web-panel/assets/{index-DAcBi6F-.js → index-OJmXGn8n.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNeLedtU.js → index-ZPdNPhUq.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ds1BfT_C.js → index-djikyGz3.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbF27GqZ.js → index-gQWWnP3_.js} +1 -1
- package/src/assets/web-panel/assets/{index-CnhJbWsl.js → index-owVbLJnH.js} +1 -1
- package/src/assets/web-panel/assets/{index-Lh0e2afo.js → index-tWEDZHzj.js} +1 -1
- package/src/assets/web-panel/assets/{index-DbjUePgq.js → index-ttTl376q.js} +1 -1
- package/src/assets/web-panel/assets/{index-nuZaMsMy.js → index-z5rYvstM.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C_pMmi75.js → initDefaultProps-B_Uk-wzg.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BvKgS96o.js → motion-ez7H8y8b.js} +1 -1
- package/src/assets/web-panel/assets/{move-C-1U4R1I.js → move-CKWUhhhX.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-D6BC968-.js → mtc-parser-DkNl7ocj.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DxOZvcGQ.js → omit-C0uA2ytw.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-LpzmHdkw.js → pickAttrs-CySaWMPl.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-Bm--qom1.js → placementArrow-BWL3-3xo.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CVZZlhJf.js → responsiveObserve-BRy_wJog.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DD1xINGx.js → slide-DmmvSaBD.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-LrjHAy63.js → statusUtils-C_7SYY0K.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BqknYxTe.js → styleChecker-DYgBJGDh.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Dljy3diT.js → useFlexGapSupport-nPkoZkT6.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-OedUtPLc.js → useFs-CzpsfP4_.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-Cz-kxbP_.js → usePersonalDataHub-Bo2vvMWl.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-C8ZuTk8W.js → vnode-D-hXAnHb.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CjbgJYXQ.js → zoom-DW7seTGG.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +39 -14
- package/src/commands/background-session.js +12 -1
- package/src/commands/permissions.js +24 -1
- package/src/gateways/ws/ws-agent-handler.js +26 -1
- package/src/gateways/ws/ws-approval-gate.js +131 -0
- package/src/index.js +63 -845
- package/src/lib/agent-ipc-bus.js +331 -0
- package/src/lib/agent-worktree.js +173 -5
- package/src/lib/background-agent-supervisor.js +193 -4
- 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/interaction-adapter.js +26 -5
- package/src/lib/metrics-collector.js +127 -0
- package/src/lib/observability/index.js +33 -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/repl-rewind.js +62 -14
- package/src/lib/runtime-provenance-ledger.js +190 -0
- package/src/lib/session-hooks.js +41 -0
- package/src/lib/turn-binding.js +134 -0
- package/src/program-base.js +4 -1
- package/src/repl/agent-repl.js +77 -3
- package/src/repl/repl-turn-binding.js +91 -0
- package/src/runtime/headless-runner.js +59 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-RQ1HbTY5.js +0 -1
- package/src/assets/web-panel/assets/devWarning-Cwuuqc_f.js +0 -1
- package/src/assets/web-panel/assets/index-BkTk-zJK.js +0 -1
- package/src/assets/web-panel/assets/index-C-BSTEhe.js +0 -1
|
@@ -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 };
|
|
@@ -187,7 +187,7 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
|
|
|
187
187
|
});
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
_ask(questionType, question, extra = {}) {
|
|
190
|
+
_ask(questionType, question, extra = {}, options = {}) {
|
|
191
191
|
return this._request(
|
|
192
192
|
{
|
|
193
193
|
type: "question",
|
|
@@ -195,7 +195,12 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
|
|
|
195
195
|
question,
|
|
196
196
|
...extra,
|
|
197
197
|
},
|
|
198
|
-
{
|
|
198
|
+
{
|
|
199
|
+
kind: "question",
|
|
200
|
+
// Approval binding pass-through: a permission-gate producer supplies
|
|
201
|
+
// it; plain questions never carry one (message stays byte-identical).
|
|
202
|
+
...(options.binding ? { binding: options.binding } : {}),
|
|
203
|
+
},
|
|
199
204
|
);
|
|
200
205
|
}
|
|
201
206
|
|
|
@@ -207,10 +212,26 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
|
|
|
207
212
|
return this._ask("select", question, { choices });
|
|
208
213
|
}
|
|
209
214
|
|
|
215
|
+
/**
|
|
216
|
+
* @param {string} question
|
|
217
|
+
* @param {boolean|object} [defaultVal] — boolean (legacy, byte-identical) or
|
|
218
|
+
* an options object `{ default, binding, ...extra }`: `binding` rides out
|
|
219
|
+
* on the request so the client must echo it back on approve (verified in
|
|
220
|
+
* _resolvePending, mismatched approve → deny); any other keys merge into
|
|
221
|
+
* the question message (e.g. structured `approval` context for UIs).
|
|
222
|
+
*/
|
|
210
223
|
async askConfirm(question, defaultVal = true) {
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
224
|
+
const opts =
|
|
225
|
+
defaultVal !== null && typeof defaultVal === "object"
|
|
226
|
+
? defaultVal
|
|
227
|
+
: { default: defaultVal };
|
|
228
|
+
const { binding = null, default: dflt = true, ...extra } = opts;
|
|
229
|
+
const answer = await this._ask(
|
|
230
|
+
"confirm",
|
|
231
|
+
question,
|
|
232
|
+
{ default: dflt, ...extra },
|
|
233
|
+
binding ? { binding } : {},
|
|
234
|
+
);
|
|
214
235
|
// Normalize to boolean
|
|
215
236
|
if (typeof answer === "boolean") return answer;
|
|
216
237
|
return answer === "true" || answer === "yes" || answer === "y";
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MetricsCollector - CLI runtime metrics collection (M3)
|
|
3
|
+
*
|
|
4
|
+
* Collects counters, gauges, histograms for performance monitoring
|
|
5
|
+
* with OTLP export support (M8 integration)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class MetricsCollector {
|
|
9
|
+
constructor() {
|
|
10
|
+
this._counters = new Map();
|
|
11
|
+
this._gauges = new Map();
|
|
12
|
+
this._histograms = new Map();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Increment a counter metric
|
|
17
|
+
* @param {string} name - Metric name
|
|
18
|
+
* @param {number} value - Increment amount (default 1)
|
|
19
|
+
* @param {object} labels - Metric labels
|
|
20
|
+
*/
|
|
21
|
+
counter(name, value = 1, labels = {}) {
|
|
22
|
+
const key = this._key(name, labels);
|
|
23
|
+
if (!this._counters.has(key)) {
|
|
24
|
+
this._counters.set(key, { name, value: 0, labels });
|
|
25
|
+
}
|
|
26
|
+
this._counters.get(key).value += value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Set a gauge metric
|
|
31
|
+
* @param {string} name - Metric name
|
|
32
|
+
* @param {number} value - Gauge value
|
|
33
|
+
* @param {object} labels - Metric labels
|
|
34
|
+
*/
|
|
35
|
+
gauge(name, value, labels = {}) {
|
|
36
|
+
const key = this._key(name, labels);
|
|
37
|
+
this._gauges.set(key, { name, value, labels, timestamp: Date.now() });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Record a histogram observation
|
|
42
|
+
* @param {string} name - Metric name
|
|
43
|
+
* @param {number} value - Observed value
|
|
44
|
+
* @param {object} labels - Metric labels
|
|
45
|
+
*/
|
|
46
|
+
histogram(name, value, labels = {}) {
|
|
47
|
+
const key = this._key(name, labels);
|
|
48
|
+
if (!this._histograms.has(key)) {
|
|
49
|
+
this._histograms.set(key, {
|
|
50
|
+
name,
|
|
51
|
+
count: 0,
|
|
52
|
+
sum: 0,
|
|
53
|
+
min: Infinity,
|
|
54
|
+
max: -Infinity,
|
|
55
|
+
labels,
|
|
56
|
+
values: [],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const h = this._histograms.get(key);
|
|
60
|
+
h.count++;
|
|
61
|
+
h.sum += value;
|
|
62
|
+
h.min = Math.min(h.min, value);
|
|
63
|
+
h.max = Math.max(h.max, value);
|
|
64
|
+
// Keep last 1000 values for percentile calculation
|
|
65
|
+
if (h.values.length < 1000) {
|
|
66
|
+
h.values.push(value);
|
|
67
|
+
} else {
|
|
68
|
+
h.values[h.count % 1000] = value;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Time a function execution and record duration
|
|
74
|
+
* @param {string} name - Metric name
|
|
75
|
+
* @param {Function} fn - Function to time
|
|
76
|
+
* @param {object} labels - Metric labels
|
|
77
|
+
* @returns {*} Function result
|
|
78
|
+
*/
|
|
79
|
+
async timer(name, fn, labels = {}) {
|
|
80
|
+
const start = performance.now();
|
|
81
|
+
try {
|
|
82
|
+
return await fn();
|
|
83
|
+
} finally {
|
|
84
|
+
const duration = performance.now() - start;
|
|
85
|
+
this.histogram(name, duration, labels);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get all collected metrics
|
|
91
|
+
* @returns {object} All metrics
|
|
92
|
+
*/
|
|
93
|
+
snapshot() {
|
|
94
|
+
return {
|
|
95
|
+
counters: Array.from(this._counters.values()),
|
|
96
|
+
gauges: Array.from(this._gauges.values()),
|
|
97
|
+
histograms: Array.from(this._histograms.values()).map((h) => ({
|
|
98
|
+
name: h.name,
|
|
99
|
+
count: h.count,
|
|
100
|
+
sum: h.sum,
|
|
101
|
+
min: h.min,
|
|
102
|
+
max: h.max,
|
|
103
|
+
avg: h.sum / h.count,
|
|
104
|
+
labels: h.labels,
|
|
105
|
+
})),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Reset all metrics
|
|
111
|
+
*/
|
|
112
|
+
reset() {
|
|
113
|
+
this._counters.clear();
|
|
114
|
+
this._gauges.clear();
|
|
115
|
+
this._histograms.clear();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
_key(name, labels) {
|
|
119
|
+
const labelStr = Object.entries(labels)
|
|
120
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
121
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
122
|
+
.join(",");
|
|
123
|
+
return labelStr ? `${name}{${labelStr}}` : name;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const metricsCollector = new MetricsCollector();
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observability - 可观测性模块统一导出
|
|
3
|
+
* M3 阶段模块:包含分布式追踪 + 性能指标收集
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
traceContext,
|
|
8
|
+
TraceContext,
|
|
9
|
+
} from "../execution-trace/trace-context.js";
|
|
10
|
+
export {
|
|
11
|
+
metricsCollector,
|
|
12
|
+
MetricsCollector,
|
|
13
|
+
} from "../execution-trace/metrics-collector.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 初始化可观测性模块
|
|
17
|
+
* @param {Object} [options={}]
|
|
18
|
+
* @param {boolean} [options.tracingEnabled=true] - 是否启用追踪
|
|
19
|
+
* @param {boolean} [options.metricsEnabled=true] - 是否启用指标收集
|
|
20
|
+
*/
|
|
21
|
+
export function initObservability(options = {}) {
|
|
22
|
+
const { tracingEnabled = true, metricsEnabled = true } = options;
|
|
23
|
+
|
|
24
|
+
// 可观测性模块初始化逻辑
|
|
25
|
+
return {
|
|
26
|
+
tracingEnabled,
|
|
27
|
+
metricsEnabled,
|
|
28
|
+
traceContext,
|
|
29
|
+
metricsCollector,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export * from "../execution-trace/index.js";
|