chainlesschain 0.162.184 → 0.162.186
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 +21 -5
- package/package.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-DzAl5fGx.js → AIOps-DTXiO-TB.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DAHtMGeC.js → ActionButton-BbmDW-Pz.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BHQaexv5.js → Analytics-CSfgXJuD.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-Phz_pD0n.js → AppLayout-BS0FFLb7.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-DHt47kCA.js → Artifacts-B19WFPK2.js} +1 -1
- package/src/assets/web-panel/assets/{Audit--Y_8FULl.js → Audit-CQhZBi7n.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-N1zchH7D.js → BackgroundAgents-BOLZCpGH.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-B_BoyN30.js → Backup-Dz3R_ePD.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Gl4uQHcT.js → BaseInput-CYfgMpax.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-Cg-eIyAZ.js → Chat-5CymMr1j.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BzeUi3JH.js → ChatBubbleRenderer-B2A_TJtl.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-D0MGR2wA.js → Checkbox-CDXHVYcu.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BdObJEFn.js → Codegen-uq9Z9oiS.js} +1 -1
- package/src/assets/web-panel/assets/{Col--ghxXT3z.js → Col-BuW1K_65.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BUFeXOqz.js → Community-Bbg-PvRY.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DhQO2_Mr.js → Compact-co0pPPTw.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-3HoTqKYd.js → Compliance-B-pFAv7z.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-B3zmO4VW.js → Cowork-54ZQS4gC.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-CJDrZOcc.js → Cron-B1BF2Seq.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-SMl15obd.js → Crosschain-Dey6qQTj.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CgTrTWrl.js → DID-ltERvfAj.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-dXFKaKol.js → Dashboard-DuWxOcqN.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-qQHEhCvI.js → Dropdown-CrZ8ekTo.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-C_ej0MWH.js → EmailListRenderer-fIyHXhUS.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-D6ajoE7I.js → FamilyGuardDashboard-BtRyrRPm.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Cio2y49h.js → Federation-EUC6Wydj.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-ClJz1cYp.js → FormItemContext-BOpka2G-.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-Dr_8Maca.js → GenericCardRenderer-BXtrrYHn.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CevLwp5u.js → Git-8WMb7PiR.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CW9YyQUY.js → Governance-DNWOeDTV.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CQqeAyER.js → Inference-Bne9b_Lb.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-cuGVxHrw.js → KnowledgeGraph-Cp-6Us9b.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DIuHri5d.js → Logs-Ck-uSPAx.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-R91Qw5JA.js → MarkdownRenderer-DxjsdAXw.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-CY1VhFGx.js → Marketplace-Vdq7brrJ.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BBAbi1vt.js → McpTools-ggLnw4hY.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-BWYGHxJA.js → Memory-BKIjNVCU.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CPIFkW7A.js → MobileBridge-BElCY25a.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DQyWxCho.js → MobileProjects-DpLgovdH.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-Bo3GnQbx.js → Mtc-bNdrF_Zh.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-C8-c4rSW.js → MtcAudit-Dw8w1Cqk.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-ChqMTtoq.js → Multisig-CfGKvlqc.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-C48TyhvI.js → NLProgramming-D6OJf7YN.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-zogUFWxp.js → Notes-B3n_LzIS.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-v-K9XpSB.js → NotificationSettings-Uu1V_ICM.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-aGyWwWcp.js +1 -0
- package/src/assets/web-panel/assets/{Organization-Boi1dQfR.js → Organization-qjFFmuwK.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-DM5YWwgA.js → Overflow-J4ZgnwZz.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-C57LKpKK.js → P2P-YEZF-uVH.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BFkPqu6Q.js → PdhVaultBrowser-yqx7fwqk.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-D6fB9YSp.js → Permissions-DdEjlZ02.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Bp7EefEo.js → PersonalDataHub-DbAPYvns.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-BVln3hZf.js → Pipeline-Bxlk_ZJU.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Bf8P3nF6.js → Privacy-CpE-05tz.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DM6wA2E2.js → ProjectInit-DWlhcGHT.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CGbc5Iho.js → ProjectSettings-BnH6BiTQ.js} +2 -2
- package/src/assets/web-panel/assets/Projects-CHw2fXDB.js +1 -0
- package/src/assets/web-panel/assets/{Providers-B2kJcxS6.js → Providers-Bdz_sTyb.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Cxu7dXYy.js → QrScannerModal-BHh2b3pH.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DTlrTPms.js → QuickAsk-D84M09pe.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Cm3X-cCj.js → Recommend-Czc30K6M.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DNb2kjvt.js → RemoteSession-q_TU5qxt.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CZUFFjc1.js → Reputation-CnTce4WM.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DAPgloxA.js → Row-C60UFLay.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-dsx9S793.js → RssFeed-BOUFEu9_.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DnnC8kz-.js → Search-D4__blE-.js} +1 -1
- package/src/assets/web-panel/assets/{Security-VuZknRMK.js → Security-BoOm7_JK.js} +4 -4
- package/src/assets/web-panel/assets/{Services-B3XNX8Vx.js → Services-AvzrW6HA.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Dl1nVtCj.js → Skeleton-BZlrHozi.js} +1 -1
- package/src/assets/web-panel/assets/Skills-CKaMLuk0.js +1 -0
- package/src/assets/web-panel/assets/{Sla-HzU56n1z.js → Sla-DYCNJF8h.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-BvCSU6d2.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-DWadz7Sl.js → SyncSettings-B6tW8dVO.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-oNC_2NVz.js +1 -0
- package/src/assets/web-panel/assets/{Templates-BBAgvrlM.js → Templates-DvLPvvjL.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BsbbvGN8.js → Tenant-BDt9UBn_.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-uscpZI4q.js → Terminal-ddCUnH-X.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-mTyjNE6r.js → TimelineRenderer-CA4sDL47.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Bz97oBgs.js → Tokens-By03hnVs.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BTRKC140.js → Trigger-Dz2mH-ZU.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-B4A_KuyX.js → Trust-DShSWHeJ.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DES9TmLU.js → UkeySign-JN8lP3gt.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-ClXs7Qxp.js → VideoEditing-BgPy4gV3.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-IzZqFz-7.js → Wallet-CQGEsXw_.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-BtOP04g_.js → WebAuthn-D8xR-ds_.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-_0cqnopY.js → WorkflowEditor-Df61_p1p.js} +1 -1
- package/src/assets/web-panel/assets/{chat-ax7193uU.js → chat-fdPby427.js} +1 -1
- package/src/assets/web-panel/assets/{colors-Bu4WGJah.js → colors-Dimqhufo.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BX1dlTW0.js → compact-item-DHayKcqZ.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CktA_Dj7.js → createContext-hZUhypRH.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-ePxoBp92.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-D5wd817l.js → hasIn-Dw7zC_dV.js} +1 -1
- package/src/assets/web-panel/assets/{index-ytp2H5Zx.js → index--DVpUpz3.js} +1 -1
- package/src/assets/web-panel/assets/{index-tYPSfGbd.js → index-BHJUptSx.js} +1 -1
- package/src/assets/web-panel/assets/{index-A4uBPQCz.js → index-BIxxvaGN.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmVUeWUk.js → index-BM5BNqp9.js} +1 -1
- package/src/assets/web-panel/assets/index-BTBDdCoo.js +1 -0
- package/src/assets/web-panel/assets/{index-Ci3y1ZAv.js → index-BVHdkz8F.js} +17 -17
- package/src/assets/web-panel/assets/{index-Cv7bVWJ5.js → index-BYdNwhkv.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUForzsG.js → index-Bhmpp3cm.js} +1 -1
- package/src/assets/web-panel/assets/{index-C8DkWyNr.js → index-BuMSdwno.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRfXoRuh.js → index-BwnUluxu.js} +1 -1
- package/src/assets/web-panel/assets/{index-o14NNnxw.js → index-C1yMYKi-.js} +1 -1
- package/src/assets/web-panel/assets/{index-C8tLR2TG.js → index-C8XXPz2n.js} +1 -1
- package/src/assets/web-panel/assets/{index-5PYYkr8S.js → index-CBQK-LEF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DkD1K-fF.js → index-CIOE8tt6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CjEJjl5C.js → index-CPZ3U5iH.js} +1 -1
- package/src/assets/web-panel/assets/{index-D9op8poE.js → index-CTiVrnpR.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYo9S5OT.js → index-CcpDboUd.js} +1 -1
- package/src/assets/web-panel/assets/index-CfOGx8Ns.js +1 -0
- package/src/assets/web-panel/assets/{index-D-o4Rwjk.js → index-CsCo_A0I.js} +1 -1
- package/src/assets/web-panel/assets/{index-h3KJZr95.js → index-CuaVa8tX.js} +1 -1
- package/src/assets/web-panel/assets/{index-DF1TysIF.js → index-CvKHpaao.js} +1 -1
- package/src/assets/web-panel/assets/{index-CfzxR61U.js → index-D7jQEwB6.js} +1 -1
- package/src/assets/web-panel/assets/{index-Db3CpWTr.js → index-DAjvkQ4K.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmPJ6T6F.js → index-DC2R4zvM.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3HmzCLt.js → index-DJP7TjWm.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAtBNfkr.js → index-DSlda4-6.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_cycOET.js → index-DVM_KgzF.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dzdr0zDf.js → index-DX1DwDFI.js} +1 -1
- package/src/assets/web-panel/assets/{index-bFCaM7Eq.js → index-DnwDYBnG.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bad2CYeH.js → index-DxoPzPHy.js} +1 -1
- package/src/assets/web-panel/assets/{index-iHcPtQ2e.js → index-DyEhrvpm.js} +1 -1
- package/src/assets/web-panel/assets/{index-jzlzm4VK.js → index-DzJXkoZs.js} +1 -1
- package/src/assets/web-panel/assets/{index-CP8ELJBc.js → index-JE6Udz2r.js} +1 -1
- package/src/assets/web-panel/assets/{index-CeVZaJSC.js → index-Urt2R5aE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BLLsUNDc.js → index-cEnkyHtX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcGdL3sG.js → index-j0pxzOu7.js} +1 -1
- package/src/assets/web-panel/assets/{index-PBLzwYnu.js → index-lPjBQJCy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CYRE0NJA.js → index-ulvBurtw.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQtoRyTO.js → index-y5LHQ5Ck.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-D2Iut83B.js → initDefaultProps-B4pR3px5.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BuaJVdK9.js → motion-CE7dWPPs.js} +1 -1
- package/src/assets/web-panel/assets/{move-CaEuM92y.js → move-Hw8cUObc.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-wuKNBwys.js → mtc-parser-CvRjE4Fa.js} +1 -1
- package/src/assets/web-panel/assets/{omit-7a3eklaq.js → omit-CkFeLVre.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Dn4z7KvJ.js → pickAttrs-5ZMqmbbb.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DVn_qT4-.js → placementArrow-BZ4oeZ0q.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Dodz1OMQ.js → responsiveObserve-lwoiX_Us.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CxoGskbp.js → slide-D_as37l8.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DBh_epQH.js → statusUtils-BsA-4Bs7.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-RXQ4gG5P.js → styleChecker-BVar4sLQ.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-1Wxm0HEG.js → useFlexGapSupport-j1IwVpGd.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CI_ySNxJ.js → useFs-7Q9bEnUp.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-C6973rZP.js → usePersonalDataHub-BBwTIRMd.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CalynwKr.js → vnode-C7Dk-M8w.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CM1cd-Q0.js → zoom-D3UHaTFB.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/eval.js +7 -0
- package/src/commands/status.js +55 -0
- package/src/commands/team.js +31 -6
- package/src/data/changelog.json +26 -0
- package/src/gateways/terminal/PtyManager.js +45 -15
- package/src/gateways/ws/ws-agent-handler.js +15 -1
- package/src/gateways/ws/ws-session-gateway.js +88 -0
- package/src/harness/background-task-command-runner.js +234 -0
- package/src/harness/background-task-manager.js +150 -20
- package/src/harness/background-task-worker.js +17 -11
- package/src/lazy-dispatch.js +12 -1
- package/src/lib/background-agent-supervisor.js +46 -0
- package/src/lib/event-runtime-host.js +12 -10
- package/src/lib/hook-runner.cjs +7 -1
- package/src/lib/hook-runner.js +2 -0
- package/src/lib/hooks-v2-runtime.js +94 -6
- package/src/lib/hooks-v2-workspace-context.js +258 -0
- package/src/lib/lsp/__tests__/lsp-client.test.js +1 -4
- package/src/lib/observability/index.js +191 -19
- package/src/lib/observability/otlp-exporter.js +1209 -207
- package/src/lib/otlp-exporter.js +8 -36
- package/src/lib/plugin-runtime/bin.js +20 -4
- package/src/lib/plugin-runtime/sandbox-policy.js +1 -4
- package/src/lib/process-execution-broker/index.js +706 -40
- package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +212 -2
- package/src/lib/process-execution-broker/linux-generic-bwrap.js +124 -6
- package/src/lib/process-execution-broker/platform-sandbox.js +6 -2
- package/src/program-base.js +9 -2
- package/src/repl/agent-repl.js +8 -0
- package/src/runtime/agent-core.js +272 -38
- package/src/runtime/headless-runner.js +56 -22
- package/src/runtime/headless-stream.js +8 -0
- package/src/workers/background-agent-worker.js +5 -11
- package/src/assets/web-panel/assets/OrderTableRenderer-P94JGGNz.js +0 -1
- package/src/assets/web-panel/assets/Projects-D0p9EOxm.js +0 -1
- package/src/assets/web-panel/assets/Skills-CY09CZ_W.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-CRle7Jl2.js +0 -1
- package/src/assets/web-panel/assets/Tasks-CNVTDGg8.js +0 -1
- package/src/assets/web-panel/assets/devWarning-x877iKKS.js +0 -1
- package/src/assets/web-panel/assets/index-D7pfPouB.js +0 -1
- package/src/assets/web-panel/assets/index-e4i-VLBd.js +0 -1
package/src/lib/hook-runner.js
CHANGED
|
@@ -107,6 +107,8 @@ function runHookProcessSync(file, argsOrOptions, maybeOptions) {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
export function _restoreProcessRunners() {
|
|
110
|
+
_processDeps.run = brokerRunner;
|
|
111
|
+
_processDeps.runSync = brokerSyncRunner;
|
|
110
112
|
runner._deps.run = runHookProcess;
|
|
111
113
|
runner._deps.runSync = runHookProcessSync;
|
|
112
114
|
}
|
|
@@ -30,6 +30,13 @@ import hookShellCommand from "./hook-shell-command.cjs";
|
|
|
30
30
|
import permissionRules from "./permission-rules.cjs";
|
|
31
31
|
import { EventRuntimeStore } from "./event-runtime-store.js";
|
|
32
32
|
import { getDefaultEventRuntimeHost } from "./event-runtime-host.js";
|
|
33
|
+
import {
|
|
34
|
+
currentHostHooksV2WorkspaceBinding,
|
|
35
|
+
currentHostHooksV2WorkspaceRoot,
|
|
36
|
+
registerHostHooksV2Workspace,
|
|
37
|
+
resolveRegisteredHostHooksV2Workspace,
|
|
38
|
+
runWithHostHooksV2Workspace,
|
|
39
|
+
} from "./hooks-v2-workspace-context.js";
|
|
33
40
|
|
|
34
41
|
const { globToRegExp } = permissionRules;
|
|
35
42
|
const { buildManagedHookEnvironment } = hookEnvironment;
|
|
@@ -279,12 +286,31 @@ export function resolveHookSandboxPolicy(hook = {}, managedPolicy = {}) {
|
|
|
279
286
|
return resolved;
|
|
280
287
|
}
|
|
281
288
|
|
|
289
|
+
function hooksRequireWorkspaceBinding(hooks, managedPolicy) {
|
|
290
|
+
return hooks.some((hook) => {
|
|
291
|
+
if (hook.type !== "command") return false;
|
|
292
|
+
try {
|
|
293
|
+
return requiresExplicitHookShell(
|
|
294
|
+
resolveHookSandboxPolicy(hook, managedPolicy),
|
|
295
|
+
);
|
|
296
|
+
} catch {
|
|
297
|
+
// Invalid policy is fail-closed during execution and must remain so after
|
|
298
|
+
// a crash instead of recovering without workspace authority.
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
282
304
|
export function assertManagedHookPolicy(
|
|
283
305
|
hook,
|
|
284
306
|
context = {},
|
|
285
307
|
policy = {},
|
|
286
308
|
configDir = null,
|
|
309
|
+
workspaceRoot = null,
|
|
287
310
|
) {
|
|
311
|
+
// Retained for API compatibility and non-command policy growth, but never
|
|
312
|
+
// consulted as filesystem authority.
|
|
313
|
+
void context;
|
|
288
314
|
if (!managedListAllows(hook.type, policy.allowedExecutors)) {
|
|
289
315
|
throw managedPolicyError(
|
|
290
316
|
`Hook executor is outside the managed allowlist: ${hook.type}`,
|
|
@@ -309,7 +335,11 @@ export function assertManagedHookPolicy(
|
|
|
309
335
|
`Hook command is outside the managed allowlist: ${executable}`,
|
|
310
336
|
);
|
|
311
337
|
}
|
|
312
|
-
|
|
338
|
+
// The event context is payload, not workspace authority. Keep this
|
|
339
|
+
// selection identical to _execCommand so a forged context.cwd cannot make
|
|
340
|
+
// the managed-root check approve a different directory than the one the
|
|
341
|
+
// Broker will actually execute in.
|
|
342
|
+
const cwd = hook.cwd || configDir || workspaceRoot || process.cwd();
|
|
313
343
|
if (!isInsideManagedRoot(cwd, policy.workspaceRoots)) {
|
|
314
344
|
throw managedPolicyError(
|
|
315
345
|
`Hook working directory is outside managed workspace roots: ${cwd}`,
|
|
@@ -435,7 +465,14 @@ class HooksV2Runtime extends EventEmitter {
|
|
|
435
465
|
// Both values are host constructor inputs. Hook definitions may select a
|
|
436
466
|
// cwd inside this root, but can never replace the writable root used to
|
|
437
467
|
// issue a Linux one-shot execution contract.
|
|
438
|
-
|
|
468
|
+
const configuredWorkspaceRoot = options.workspaceRoot || configDir || null;
|
|
469
|
+
this.workspaceBinding =
|
|
470
|
+
typeof configuredWorkspaceRoot === "string" &&
|
|
471
|
+
path.isAbsolute(configuredWorkspaceRoot)
|
|
472
|
+
? registerHostHooksV2Workspace(configuredWorkspaceRoot)
|
|
473
|
+
: null;
|
|
474
|
+
this.workspaceRoot =
|
|
475
|
+
this.workspaceBinding?.workspaceRoot || configuredWorkspaceRoot;
|
|
439
476
|
this.durableStore = options.durableStore || null;
|
|
440
477
|
this.durableOwner =
|
|
441
478
|
options.durableOwner ||
|
|
@@ -587,6 +624,12 @@ class HooksV2Runtime extends EventEmitter {
|
|
|
587
624
|
(max, hook) => Math.max(max, Number(hook?.timeoutMs) || 30000),
|
|
588
625
|
0,
|
|
589
626
|
);
|
|
627
|
+
const durableWorkspaceBinding =
|
|
628
|
+
this.workspaceBinding || currentHostHooksV2WorkspaceBinding();
|
|
629
|
+
const durableWorkspaceBindingRequired = hooksRequireWorkspaceBinding(
|
|
630
|
+
hooks,
|
|
631
|
+
this.managedPolicy,
|
|
632
|
+
);
|
|
590
633
|
const durableRecord =
|
|
591
634
|
this.durableStore && options.skipDurable !== true
|
|
592
635
|
? this.durableStore.enqueueInbox(
|
|
@@ -598,6 +641,12 @@ class HooksV2Runtime extends EventEmitter {
|
|
|
598
641
|
},
|
|
599
642
|
{
|
|
600
643
|
id: context.event_id || context.eventId || null,
|
|
644
|
+
metadata: {
|
|
645
|
+
hooksV2WorkspaceBindingId:
|
|
646
|
+
durableWorkspaceBinding?.bindingId || null,
|
|
647
|
+
hooksV2WorkspaceBindingRequired:
|
|
648
|
+
durableWorkspaceBindingRequired,
|
|
649
|
+
},
|
|
601
650
|
claimOwner: this.durableOwner,
|
|
602
651
|
leaseMs: longestHookTimeout + this.durableRecoveryBufferMs,
|
|
603
652
|
},
|
|
@@ -644,6 +693,7 @@ class HooksV2Runtime extends EventEmitter {
|
|
|
644
693
|
context,
|
|
645
694
|
this.managedPolicy,
|
|
646
695
|
this.configDir,
|
|
696
|
+
this.workspaceRoot || currentHostHooksV2WorkspaceRoot(),
|
|
647
697
|
);
|
|
648
698
|
let result;
|
|
649
699
|
switch (hook.type) {
|
|
@@ -750,9 +800,11 @@ class HooksV2Runtime extends EventEmitter {
|
|
|
750
800
|
const budget = hookBudget(hook);
|
|
751
801
|
const sandboxPolicy = resolveHookSandboxPolicy(hook, this.managedPolicy);
|
|
752
802
|
const requiresContract = requiresExplicitHookShell(sandboxPolicy);
|
|
803
|
+
const trustedRootSource =
|
|
804
|
+
this.workspaceRoot || currentHostHooksV2WorkspaceRoot();
|
|
753
805
|
const trustedRoot = requiresContract
|
|
754
806
|
? requireTrustedHookRoot(
|
|
755
|
-
|
|
807
|
+
trustedRootSource,
|
|
756
808
|
"Hooks v2 trusted workspace root",
|
|
757
809
|
)
|
|
758
810
|
: null;
|
|
@@ -1023,12 +1075,48 @@ const hooksRuntime = new HooksV2Runtime(undefined, {
|
|
|
1023
1075
|
? new EventRuntimeStore()
|
|
1024
1076
|
: null),
|
|
1025
1077
|
});
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1078
|
+
broker._setHooksEventSink(hooksRuntime);
|
|
1079
|
+
|
|
1080
|
+
export async function executeRecoveredHooksV2Event(
|
|
1081
|
+
runtime,
|
|
1082
|
+
event,
|
|
1083
|
+
record = {},
|
|
1084
|
+
) {
|
|
1085
|
+
const bindingId = record?.metadata?.hooksV2WorkspaceBindingId || null;
|
|
1086
|
+
const bindingRequired =
|
|
1087
|
+
record?.metadata?.hooksV2WorkspaceBindingRequired === true ||
|
|
1088
|
+
hooksRequireWorkspaceBinding(
|
|
1089
|
+
runtime?.hooks?.get?.(event?.event) || [],
|
|
1090
|
+
runtime?.managedPolicy || {},
|
|
1091
|
+
);
|
|
1092
|
+
if (!bindingId) {
|
|
1093
|
+
if (bindingRequired) {
|
|
1094
|
+
throw sandboxBoundaryError(
|
|
1095
|
+
"durable Hooks v2 recovery requires a trusted host workspace binding",
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
return runtime.executeHooks(event.event, event.context || {}, {
|
|
1099
|
+
skipDurable: true,
|
|
1100
|
+
recovered: true,
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
const binding = resolveRegisteredHostHooksV2Workspace(bindingId);
|
|
1105
|
+
if (!binding) {
|
|
1106
|
+
throw sandboxBoundaryError(
|
|
1107
|
+
"durable Hooks v2 workspace binding is not registered by this host",
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
return runWithHostHooksV2Workspace(binding.workspaceRoot, () =>
|
|
1111
|
+
runtime.executeHooks(event.event, event.context || {}, {
|
|
1029
1112
|
skipDurable: true,
|
|
1030
1113
|
recovered: true,
|
|
1031
1114
|
}),
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
defaultEventRuntimeHost?.registerHandler(
|
|
1119
|
+
(event, record) => executeRecoveredHooksV2Event(hooksRuntime, event, record),
|
|
1032
1120
|
{ queue: "inbox", type: "hooks.v2" },
|
|
1033
1121
|
);
|
|
1034
1122
|
export default hooksRuntime;
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
const trustedWorkspaceStorage = new AsyncLocalStorage();
|
|
7
|
+
const registeredHostWorkspaces = new Map();
|
|
8
|
+
const registeredHostWorkspaceIdentities = new Map();
|
|
9
|
+
const MAX_REGISTERED_HOST_WORKSPACES = 1024;
|
|
10
|
+
|
|
11
|
+
function validateHostWorkspaceRoot(workspaceRoot) {
|
|
12
|
+
if (
|
|
13
|
+
typeof workspaceRoot !== "string" ||
|
|
14
|
+
workspaceRoot.length === 0 ||
|
|
15
|
+
workspaceRoot.includes("\0")
|
|
16
|
+
) {
|
|
17
|
+
const error = new TypeError(
|
|
18
|
+
"Hooks v2 host workspace root must be a non-empty path",
|
|
19
|
+
);
|
|
20
|
+
error.code = "CC_HOOK_TRUSTED_WORKSPACE_INVALID";
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
return path.resolve(workspaceRoot);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function invalidWorkspaceRoot(message, cause) {
|
|
27
|
+
const error = new Error(message, cause ? { cause } : undefined);
|
|
28
|
+
error.code = "CC_HOOK_TRUSTED_WORKSPACE_INVALID";
|
|
29
|
+
return error;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function workspaceGeneration(stats) {
|
|
33
|
+
// dev+ino alone is insufficient: filesystems may immediately reuse an inode
|
|
34
|
+
// after a same-path directory replacement. Prefer the stable creation
|
|
35
|
+
// timestamp and fail closed on metadata changes when a filesystem does not
|
|
36
|
+
// expose one.
|
|
37
|
+
return stats.birthtimeNs > 0n
|
|
38
|
+
? `birth:${stats.birthtimeNs.toString()}`
|
|
39
|
+
: `ctime:${stats.ctimeNs.toString()}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function captureWorkspaceIdentity(workspaceRoot) {
|
|
43
|
+
const resolvedRoot = validateHostWorkspaceRoot(workspaceRoot);
|
|
44
|
+
let canonicalRoot;
|
|
45
|
+
let stats;
|
|
46
|
+
try {
|
|
47
|
+
canonicalRoot = fs.realpathSync.native(resolvedRoot);
|
|
48
|
+
stats = fs.statSync(canonicalRoot, { bigint: true });
|
|
49
|
+
} catch (error) {
|
|
50
|
+
throw invalidWorkspaceRoot(
|
|
51
|
+
"Hooks v2 host workspace root must resolve to an existing directory",
|
|
52
|
+
error,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (!stats.isDirectory()) {
|
|
56
|
+
throw invalidWorkspaceRoot(
|
|
57
|
+
"Hooks v2 host workspace root must resolve to a directory",
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return Object.freeze({
|
|
61
|
+
canonicalRoot,
|
|
62
|
+
device: stats.dev,
|
|
63
|
+
inode: stats.ino,
|
|
64
|
+
generation: workspaceGeneration(stats),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function workspaceIdentityKey(identity) {
|
|
69
|
+
return JSON.stringify([
|
|
70
|
+
identity.canonicalRoot,
|
|
71
|
+
identity.device.toString(),
|
|
72
|
+
identity.inode.toString(),
|
|
73
|
+
identity.generation,
|
|
74
|
+
]);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function sameWorkspaceIdentity(left, right) {
|
|
78
|
+
return (
|
|
79
|
+
left.canonicalRoot === right.canonicalRoot &&
|
|
80
|
+
left.device === right.device &&
|
|
81
|
+
left.inode === right.inode &&
|
|
82
|
+
left.generation === right.generation
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function invalidateWorkspaceRecord(record) {
|
|
87
|
+
if (registeredHostWorkspaces.get(record.binding.bindingId) === record) {
|
|
88
|
+
registeredHostWorkspaces.delete(record.binding.bindingId);
|
|
89
|
+
}
|
|
90
|
+
if (
|
|
91
|
+
registeredHostWorkspaceIdentities.get(record.identityKey) ===
|
|
92
|
+
record.binding.bindingId
|
|
93
|
+
) {
|
|
94
|
+
registeredHostWorkspaceIdentities.delete(record.identityKey);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function verifyWorkspaceRecord(record) {
|
|
99
|
+
if (!record) return null;
|
|
100
|
+
if (registeredHostWorkspaces.get(record.binding.bindingId) !== record) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
let currentIdentity;
|
|
104
|
+
try {
|
|
105
|
+
currentIdentity = captureWorkspaceIdentity(record.binding.workspaceRoot);
|
|
106
|
+
} catch {
|
|
107
|
+
invalidateWorkspaceRecord(record);
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
if (!sameWorkspaceIdentity(record.identity, currentIdentity)) {
|
|
111
|
+
invalidateWorkspaceRecord(record);
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
return record.binding;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function pruneInvalidWorkspaceRecords() {
|
|
118
|
+
for (const record of Array.from(registeredHostWorkspaces.values())) {
|
|
119
|
+
verifyWorkspaceRecord(record);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function workspaceBindingId(identity) {
|
|
124
|
+
return createHash("sha256")
|
|
125
|
+
.update("chainlesschain.hooks-v2-host-workspace.v3\0")
|
|
126
|
+
.update(workspaceIdentityKey(identity), "utf8")
|
|
127
|
+
.digest("hex");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Register a root selected by trusted host bootstrap code. Durable records
|
|
132
|
+
* persist only the returned opaque stable digest; the plaintext canonical path
|
|
133
|
+
* and directory identity remain process-local.
|
|
134
|
+
*/
|
|
135
|
+
export function registerHostHooksV2Workspace(workspaceRoot) {
|
|
136
|
+
const identity = captureWorkspaceIdentity(workspaceRoot);
|
|
137
|
+
const identityKey = workspaceIdentityKey(identity);
|
|
138
|
+
const existingBindingId = registeredHostWorkspaceIdentities.get(identityKey);
|
|
139
|
+
if (existingBindingId) {
|
|
140
|
+
const existingRecord = registeredHostWorkspaces.get(existingBindingId);
|
|
141
|
+
const existingBinding = verifyWorkspaceRecord(existingRecord);
|
|
142
|
+
if (
|
|
143
|
+
existingBinding &&
|
|
144
|
+
sameWorkspaceIdentity(existingRecord.identity, identity)
|
|
145
|
+
) {
|
|
146
|
+
return existingBinding;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// A long-running host may create and remove many isolated worktrees. Sweep
|
|
151
|
+
// stale filesystem identities before enforcing the bounded registry so
|
|
152
|
+
// deleted roots cannot exhaust future trusted registrations.
|
|
153
|
+
if (registeredHostWorkspaces.size >= MAX_REGISTERED_HOST_WORKSPACES) {
|
|
154
|
+
pruneInvalidWorkspaceRecords();
|
|
155
|
+
}
|
|
156
|
+
if (registeredHostWorkspaces.size >= MAX_REGISTERED_HOST_WORKSPACES) {
|
|
157
|
+
const error = new Error(
|
|
158
|
+
"Hooks v2 host workspace registry capacity exceeded",
|
|
159
|
+
);
|
|
160
|
+
error.code = "CC_HOOK_TRUSTED_WORKSPACE_LIMIT";
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const bindingId = workspaceBindingId(identity);
|
|
165
|
+
const collidingRecord = registeredHostWorkspaces.get(bindingId);
|
|
166
|
+
if (collidingRecord) {
|
|
167
|
+
const collidingBinding = verifyWorkspaceRecord(collidingRecord);
|
|
168
|
+
if (
|
|
169
|
+
collidingBinding &&
|
|
170
|
+
sameWorkspaceIdentity(collidingRecord.identity, identity)
|
|
171
|
+
) {
|
|
172
|
+
registeredHostWorkspaceIdentities.set(identityKey, bindingId);
|
|
173
|
+
return collidingBinding;
|
|
174
|
+
}
|
|
175
|
+
if (
|
|
176
|
+
collidingBinding &&
|
|
177
|
+
collidingRecord.identity.canonicalRoot !== identity.canonicalRoot
|
|
178
|
+
) {
|
|
179
|
+
const error = new Error("Hooks v2 workspace binding digest collision");
|
|
180
|
+
error.code = "CC_HOOK_TRUSTED_WORKSPACE_COLLISION";
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
if (collidingBinding) {
|
|
184
|
+
throw invalidWorkspaceRoot(
|
|
185
|
+
"Hooks v2 host workspace identity changed during registration",
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const binding = Object.freeze({
|
|
191
|
+
bindingId,
|
|
192
|
+
workspaceRoot: identity.canonicalRoot,
|
|
193
|
+
});
|
|
194
|
+
const record = Object.freeze({
|
|
195
|
+
binding,
|
|
196
|
+
identity,
|
|
197
|
+
identityKey,
|
|
198
|
+
});
|
|
199
|
+
registeredHostWorkspaces.set(bindingId, record);
|
|
200
|
+
registeredHostWorkspaceIdentities.set(identityKey, bindingId);
|
|
201
|
+
return binding;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Run a CLI-host operation with an immutable Hooks v2 workspace authority.
|
|
206
|
+
*
|
|
207
|
+
* Only host bootstrap code may call this with a root selected before hook,
|
|
208
|
+
* plugin, model, or event payload processing. The binding is async-scoped
|
|
209
|
+
* instead of global so concurrent CLI runs cannot replace one another's
|
|
210
|
+
* writable root.
|
|
211
|
+
*/
|
|
212
|
+
export function runWithHostHooksV2Workspace(workspaceRoot, callback) {
|
|
213
|
+
if (typeof callback !== "function") {
|
|
214
|
+
throw new TypeError("Hooks v2 workspace callback must be a function");
|
|
215
|
+
}
|
|
216
|
+
const binding = registerHostHooksV2Workspace(workspaceRoot);
|
|
217
|
+
return trustedWorkspaceStorage.run(
|
|
218
|
+
registeredHostWorkspaces.get(binding.bindingId),
|
|
219
|
+
callback,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Internal consumer seam for HooksV2Runtime. Event and hook payloads never
|
|
225
|
+
* participate in selecting this value.
|
|
226
|
+
*/
|
|
227
|
+
export function currentHostHooksV2WorkspaceRoot() {
|
|
228
|
+
return currentHostHooksV2WorkspaceBinding()?.workspaceRoot || null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function currentHostHooksV2WorkspaceBinding() {
|
|
232
|
+
return verifyWorkspaceRecord(trustedWorkspaceStorage.getStore());
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Resolve an opaque durable binding only against roots registered by the
|
|
237
|
+
* current host process. No persisted or event-supplied path is accepted.
|
|
238
|
+
*/
|
|
239
|
+
export function resolveRegisteredHostHooksV2Workspace(bindingId) {
|
|
240
|
+
if (typeof bindingId !== "string" || !/^[a-f0-9]{64}$/.test(bindingId)) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
return verifyWorkspaceRecord(registeredHostWorkspaces.get(bindingId));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Revoke a process-local binding owned by trusted host lifecycle code.
|
|
248
|
+
* Payloads, hooks, and durable records must never call this release seam.
|
|
249
|
+
*/
|
|
250
|
+
export function releaseRegisteredHostHooksV2Workspace(bindingId) {
|
|
251
|
+
if (typeof bindingId !== "string" || !/^[a-f0-9]{64}$/.test(bindingId)) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
const record = registeredHostWorkspaces.get(bindingId);
|
|
255
|
+
if (!record) return false;
|
|
256
|
+
invalidateWorkspaceRecord(record);
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
@@ -102,10 +102,7 @@ describe("LSPClient handshake", () => {
|
|
|
102
102
|
const child = new FakeChild();
|
|
103
103
|
const contract = Object.freeze({ kind: "test-workspace-contract" });
|
|
104
104
|
const issueAuthority = vi
|
|
105
|
-
.spyOn(
|
|
106
|
-
executionBroker,
|
|
107
|
-
"issueLinuxWorkspaceSandboxExecutionContract",
|
|
108
|
-
)
|
|
105
|
+
.spyOn(executionBroker, "issueLinuxWorkspaceSandboxExecutionContract")
|
|
109
106
|
.mockReturnValue(contract);
|
|
110
107
|
const brokerSpawn = vi
|
|
111
108
|
.spyOn(executionBroker, "spawn")
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Process-level observability runtime.
|
|
3
|
+
*
|
|
4
|
+
* It bridges the existing W3C TraceContext / MetricsCollector and the
|
|
5
|
+
* TelemetryRecorder used by agent, eval and team runs into one standard OTLP
|
|
6
|
+
* Collector exporter. Collector export is opt-in and content remains absent
|
|
7
|
+
* unless the caller explicitly enabled the existing `--otlp-content` switch.
|
|
4
8
|
*/
|
|
5
9
|
|
|
6
10
|
import {
|
|
@@ -11,25 +15,193 @@ import {
|
|
|
11
15
|
metricsCollector,
|
|
12
16
|
MetricsCollector,
|
|
13
17
|
} from "../execution-trace/metrics-collector.js";
|
|
18
|
+
import { OtlpExporter, resolveOtlpConfig } from "./otlp-exporter.js";
|
|
14
19
|
|
|
15
|
-
export {
|
|
20
|
+
export {
|
|
21
|
+
traceContext,
|
|
22
|
+
TraceContext,
|
|
23
|
+
metricsCollector,
|
|
24
|
+
MetricsCollector,
|
|
25
|
+
OtlpExporter,
|
|
26
|
+
resolveOtlpConfig,
|
|
27
|
+
};
|
|
16
28
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
function collectedMetrics(collector) {
|
|
30
|
+
const raw = collector?.collect?.();
|
|
31
|
+
const source = raw?.resourceMetrics?.[0]?.scopeMetrics?.[0]?.metrics || [];
|
|
32
|
+
return source.map((metric) => {
|
|
33
|
+
if (metric.type === "counter") {
|
|
34
|
+
return {
|
|
35
|
+
name: metric.name,
|
|
36
|
+
type: "sum",
|
|
37
|
+
value: metric.value,
|
|
38
|
+
attributes: metric.labels,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (metric.type === "histogram") {
|
|
42
|
+
return {
|
|
43
|
+
name: metric.name,
|
|
44
|
+
type: "histogram",
|
|
45
|
+
count: metric.count,
|
|
46
|
+
sum: metric.sum,
|
|
47
|
+
attributes: metric.labels,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
name: metric.name,
|
|
52
|
+
type: "gauge",
|
|
53
|
+
value: metric.value,
|
|
54
|
+
attributes: metric.labels,
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class ObservabilityRuntime {
|
|
60
|
+
constructor(options = {}, deps = {}) {
|
|
61
|
+
this.traceContext = deps.traceContext || traceContext;
|
|
62
|
+
this.metricsCollector = deps.metricsCollector || metricsCollector;
|
|
63
|
+
this.exporter =
|
|
64
|
+
deps.exporter ||
|
|
65
|
+
new OtlpExporter(
|
|
66
|
+
{
|
|
67
|
+
...options,
|
|
68
|
+
config:
|
|
69
|
+
options.config ||
|
|
70
|
+
resolveOtlpConfig(options, deps.env || process.env, deps),
|
|
71
|
+
},
|
|
72
|
+
deps,
|
|
73
|
+
);
|
|
74
|
+
this._closed = false;
|
|
75
|
+
this._onSpanEnd = (span) => this.exporter.exportSpans([span]);
|
|
76
|
+
if (this.exporter.enabled) {
|
|
77
|
+
this.traceContext.on("span:end", this._onSpanEnd);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
get enabled() {
|
|
82
|
+
return this.exporter.enabled && !this._closed;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
exportRecorder(recorder, { redact = true } = {}) {
|
|
86
|
+
if (!this.enabled || !recorder?.toOtlp) return false;
|
|
87
|
+
return this.exporter.exportTracePayload(recorder.toOtlp({ redact }), {
|
|
88
|
+
redact,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
exportTeamSummary({ workflowRunId, workflowName, summary, budget } = {}) {
|
|
93
|
+
if (!this.enabled) return false;
|
|
94
|
+
const attributes = {
|
|
95
|
+
"workflow.run_id": workflowRunId || "unknown",
|
|
96
|
+
"workflow.name": workflowName || "team",
|
|
97
|
+
};
|
|
98
|
+
const stats = summary?.stats || {};
|
|
99
|
+
const budgetStatus = budget?.status?.() || budget || {};
|
|
100
|
+
return this.exporter.exportMetrics([
|
|
101
|
+
{
|
|
102
|
+
name: "chainlesschain.team.tasks",
|
|
103
|
+
type: "gauge",
|
|
104
|
+
unit: "{task}",
|
|
105
|
+
value: Number(budgetStatus.tasks ?? summary?.executions) || 0,
|
|
106
|
+
attributes,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "chainlesschain.team.tokens",
|
|
110
|
+
type: "gauge",
|
|
111
|
+
unit: "{token}",
|
|
112
|
+
value: Number(budgetStatus.tokens) || 0,
|
|
113
|
+
attributes,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: "chainlesschain.team.cost",
|
|
117
|
+
type: "gauge",
|
|
118
|
+
unit: "USD",
|
|
119
|
+
value: Number(budgetStatus.spentUsd) || 0,
|
|
120
|
+
attributes,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "chainlesschain.team.failures",
|
|
124
|
+
type: "gauge",
|
|
125
|
+
unit: "{failure}",
|
|
126
|
+
value: Number(stats.failed) || 0,
|
|
127
|
+
attributes,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "chainlesschain.team.completed",
|
|
131
|
+
type: "gauge",
|
|
132
|
+
unit: "{task}",
|
|
133
|
+
value: Number(stats.completed) || 0,
|
|
134
|
+
attributes,
|
|
135
|
+
},
|
|
136
|
+
]);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
captureMetrics() {
|
|
140
|
+
if (!this.enabled) return false;
|
|
141
|
+
const metrics = collectedMetrics(this.metricsCollector);
|
|
142
|
+
return metrics.length > 0 ? this.exporter.exportMetrics(metrics) : false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async shutdown(options = {}) {
|
|
146
|
+
if (this._closed) return this.exporter.getStats();
|
|
147
|
+
this.traceContext.off("span:end", this._onSpanEnd);
|
|
148
|
+
this.captureMetrics();
|
|
149
|
+
const stats = await this.exporter.shutdown(options);
|
|
150
|
+
this._closed = true;
|
|
151
|
+
return stats;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
getStats() {
|
|
155
|
+
return this.exporter.getStats();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let defaultRuntime = null;
|
|
160
|
+
|
|
161
|
+
export function resolveOtlpEndpointFromArgv(argv = process.argv) {
|
|
162
|
+
const args = Array.isArray(argv) ? argv.slice(2) : [];
|
|
163
|
+
for (let index = 0; index < args.length; index++) {
|
|
164
|
+
const token = args[index];
|
|
165
|
+
if (token === "--otlp-endpoint") return args[index + 1] || null;
|
|
166
|
+
if (token.startsWith("--otlp-endpoint=")) {
|
|
167
|
+
return token.slice("--otlp-endpoint=".length) || null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function initObservability(options = {}, deps = {}) {
|
|
174
|
+
if (defaultRuntime && !defaultRuntime._closed) return defaultRuntime;
|
|
175
|
+
defaultRuntime = new ObservabilityRuntime(options, deps);
|
|
176
|
+
return defaultRuntime;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function getObservabilityRuntime() {
|
|
180
|
+
return defaultRuntime;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function isOtlpCollectorEnabled() {
|
|
184
|
+
return defaultRuntime?.enabled === true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function exportTelemetryRecorder(recorder, options) {
|
|
188
|
+
return defaultRuntime?.exportRecorder(recorder, options) || false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function exportTeamTelemetry(summary) {
|
|
192
|
+
return defaultRuntime?.exportTeamSummary(summary) || false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export async function shutdownObservability(options) {
|
|
196
|
+
return defaultRuntime?.shutdown(options);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Test-only lifecycle seam; avoids state leaking across module-level tests. */
|
|
200
|
+
export async function resetObservabilityForTests() {
|
|
201
|
+
if (defaultRuntime && !defaultRuntime._closed) {
|
|
202
|
+
await defaultRuntime.shutdown({ timeoutMs: 0 });
|
|
203
|
+
}
|
|
204
|
+
defaultRuntime = null;
|
|
33
205
|
}
|
|
34
206
|
|
|
35
207
|
export * from "../execution-trace/index.js";
|