@wrongstack/core 0.313.0 → 0.316.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (343) hide show
  1. package/dist/chronicle/index.js +84 -6391
  2. package/dist/chronicle/legacy-journal-import.d.ts +0 -6
  3. package/dist/chronicle/metrics-ingest.d.ts +14 -0
  4. package/dist/chronicle/metrics-schema.d.ts +10 -1
  5. package/dist/chronicle/metrics-store.d.ts +20 -2
  6. package/dist/chronicle/project-server.js +98 -4874
  7. package/dist/chronicle/sink.d.ts +13 -0
  8. package/dist/chronicle/sqlite-journal.d.ts +20 -2
  9. package/dist/chunk-25LSLC3G.js +334 -0
  10. package/dist/chunk-27QIURLN.js +1073 -0
  11. package/dist/chunk-2FK4WTFF.js +207 -0
  12. package/dist/chunk-2YU4WVBW.js +134 -0
  13. package/dist/chunk-3AGWVSS7.js +4608 -0
  14. package/dist/chunk-3BDV7RL3.js +211 -0
  15. package/dist/chunk-3ECENVHL.js +325 -0
  16. package/dist/chunk-3XPVB7BA.js +135 -0
  17. package/dist/chunk-45WI72ZB.js +16884 -0
  18. package/dist/chunk-4BMVPOHO.js +3748 -0
  19. package/dist/chunk-4I7HGVVA.js +363 -0
  20. package/dist/chunk-4LCZVJVF.js +317 -0
  21. package/dist/chunk-4SJNS7OB.js +217 -0
  22. package/dist/chunk-4VWO3ZXL.js +342 -0
  23. package/dist/chunk-57GEWK5X.js +26 -0
  24. package/dist/chunk-5BMIE7QG.js +46 -0
  25. package/dist/chunk-5S437PRC.js +50 -0
  26. package/dist/chunk-5XDUGTKN.js +511 -0
  27. package/dist/chunk-5YPSXQXM.js +1443 -0
  28. package/dist/chunk-6FHWXLPT.js +10 -0
  29. package/dist/chunk-6H5GEOET.js +2406 -0
  30. package/dist/chunk-6NWEVFEL.js +151 -0
  31. package/dist/chunk-AYW5YINX.js +58 -0
  32. package/dist/chunk-B4AMLZPB.js +9 -0
  33. package/dist/chunk-B7F4WVP4.js +65 -0
  34. package/dist/chunk-BJHVULHO.js +55 -0
  35. package/dist/chunk-BPBKKBUK.js +2085 -0
  36. package/dist/chunk-BYD345OX.js +589 -0
  37. package/dist/chunk-C27KDLOX.js +279 -0
  38. package/dist/chunk-CU546ESW.js +597 -0
  39. package/dist/chunk-DLHHXUAM.js +172 -0
  40. package/dist/chunk-DOTHQSJ2.js +429 -0
  41. package/dist/chunk-DYLPMV4Q.js +711 -0
  42. package/dist/chunk-EODDVJBM.js +82 -0
  43. package/dist/chunk-F3JWNST7.js +361 -0
  44. package/dist/chunk-FC2ZGRF3.js +140 -0
  45. package/dist/chunk-FCBTO76Z.js +5786 -0
  46. package/dist/chunk-FHZVFYSL.js +396 -0
  47. package/dist/chunk-FNXVT7HF.js +52 -0
  48. package/dist/chunk-FWNLEZYG.js +259 -0
  49. package/dist/chunk-FXQLIX3K.js +211 -0
  50. package/dist/chunk-G5S3S5ZN.js +38 -0
  51. package/dist/chunk-GDQ7CZRQ.js +116 -0
  52. package/dist/chunk-GJJGJB2R.js +1374 -0
  53. package/dist/chunk-GTCU6DZ3.js +1 -0
  54. package/dist/chunk-HA4ZYA6T.js +178 -0
  55. package/dist/chunk-HI3JGZR2.js +188 -0
  56. package/dist/chunk-HMLIGAU7.js +395 -0
  57. package/dist/chunk-HXKZ7OBQ.js +303 -0
  58. package/dist/chunk-I7DALG7J.js +292 -0
  59. package/dist/chunk-IRUTEHCE.js +960 -0
  60. package/dist/chunk-IYFBFX34.js +86 -0
  61. package/dist/chunk-J2WRWR37.js +1104 -0
  62. package/dist/chunk-JFPJ6LLQ.js +129 -0
  63. package/dist/chunk-JMULFFLN.js +9 -0
  64. package/dist/chunk-JO4MGB6Z.js +1264 -0
  65. package/dist/chunk-JVKB2TBL.js +48 -0
  66. package/dist/chunk-JX7VBZDK.js +31 -0
  67. package/dist/chunk-K6BXGITA.js +931 -0
  68. package/dist/chunk-KDQJCT2J.js +45 -0
  69. package/dist/chunk-KJ2ZTQSL.js +47 -0
  70. package/dist/chunk-KZMSZV32.js +187 -0
  71. package/dist/chunk-LA7CLDRW.js +129 -0
  72. package/dist/chunk-LHOA5DWA.js +28 -0
  73. package/dist/chunk-LSMMOS2Z.js +119 -0
  74. package/dist/chunk-LX5QWLBL.js +27 -0
  75. package/dist/chunk-LXWCMQNJ.js +789 -0
  76. package/dist/chunk-M27LIEKV.js +2829 -0
  77. package/dist/chunk-MJPHJRAL.js +663 -0
  78. package/dist/chunk-MTL4O5YR.js +228 -0
  79. package/dist/chunk-NDBIPFNY.js +102 -0
  80. package/dist/chunk-NEMIWEXC.js +1199 -0
  81. package/dist/chunk-NHJ75HXY.js +1 -0
  82. package/dist/chunk-NIOAGFPB.js +1510 -0
  83. package/dist/chunk-NS52ISWS.js +355 -0
  84. package/dist/chunk-NSOFPKGT.js +5352 -0
  85. package/dist/chunk-NVIZG63R.js +757 -0
  86. package/dist/chunk-O37BQ65S.js +21 -0
  87. package/dist/chunk-OOYZPOXM.js +308 -0
  88. package/dist/chunk-OSGLYW5J.js +28 -0
  89. package/dist/chunk-PFCMSSUF.js +1640 -0
  90. package/dist/chunk-PFZO3C6H.js +14 -0
  91. package/dist/chunk-PGRVXIMY.js +158 -0
  92. package/dist/chunk-PHH7LD45.js +113 -0
  93. package/dist/chunk-PISBFBJN.js +109 -0
  94. package/dist/chunk-POOOIWE5.js +9 -0
  95. package/dist/chunk-PQNS3U7Z.js +1240 -0
  96. package/dist/chunk-QSTSJOCK.js +402 -0
  97. package/dist/chunk-RFQMOKWK.js +282 -0
  98. package/dist/chunk-RJIZEJ42.js +30 -0
  99. package/dist/chunk-RTOTNK3P.js +484 -0
  100. package/dist/chunk-RXKSDUAU.js +1172 -0
  101. package/dist/chunk-S45JPKQN.js +3317 -0
  102. package/dist/chunk-SAFHACFA.js +152 -0
  103. package/dist/chunk-SQJVFEBV.js +41 -0
  104. package/dist/chunk-SVT4RNTR.js +49 -0
  105. package/dist/chunk-SWUFN7XM.js +43 -0
  106. package/dist/chunk-SXMVYC5H.js +174 -0
  107. package/dist/chunk-SZ6AYUIZ.js +23 -0
  108. package/dist/chunk-TBRL2RCC.js +9 -0
  109. package/dist/chunk-TCCHINEF.js +655 -0
  110. package/dist/chunk-THEGTRAG.js +316 -0
  111. package/dist/chunk-TR4LQE7L.js +2256 -0
  112. package/dist/chunk-TRT2HRWZ.js +18 -0
  113. package/dist/chunk-TWJMGXCI.js +900 -0
  114. package/dist/chunk-U45AXT5D.js +1023 -0
  115. package/dist/chunk-U4FWBSO3.js +509 -0
  116. package/dist/chunk-UBGDX6FI.js +147 -0
  117. package/dist/chunk-UJILKYES.js +75 -0
  118. package/dist/chunk-ULMAQV3T.js +234 -0
  119. package/dist/chunk-VVBP6KGT.js +9 -0
  120. package/dist/chunk-W5VITDM2.js +168 -0
  121. package/dist/chunk-WAHQVNWT.js +194 -0
  122. package/dist/chunk-WBPVKEEE.js +227 -0
  123. package/dist/chunk-WGGYRBXH.js +5434 -0
  124. package/dist/chunk-WLYKU45P.js +5770 -0
  125. package/dist/chunk-WP7BVANJ.js +53 -0
  126. package/dist/chunk-WQQON3SV.js +473 -0
  127. package/dist/chunk-WQTOVMGV.js +119 -0
  128. package/dist/chunk-XBDP4QPW.js +7 -0
  129. package/dist/chunk-XEBHSJLT.js +103 -0
  130. package/dist/chunk-XF4VUMKW.js +231 -0
  131. package/dist/chunk-XQ2ECNK3.js +1 -0
  132. package/dist/chunk-Y5YXDM7D.js +74 -0
  133. package/dist/chunk-YOPAUVU4.js +2588 -0
  134. package/dist/chunk-YPT4A46F.js +362 -0
  135. package/dist/chunk-YWXT3JNM.js +425 -0
  136. package/dist/chunk-YY6APYUM.js +49 -0
  137. package/dist/chunk-Z2IJUO45.js +691 -0
  138. package/dist/chunk-ZHZ2Y2DH.js +1643 -0
  139. package/dist/chunk-ZT7JNIGX.js +152 -0
  140. package/dist/coordination/agent-monitor.d.ts +9 -1
  141. package/dist/coordination/agent-subagent-runner.d.ts +1 -1
  142. package/dist/coordination/agents/agent-prompts.d.ts +8 -0
  143. package/dist/coordination/agents/index.d.ts +33 -1
  144. package/dist/coordination/agents/index.js +184 -6269
  145. package/dist/coordination/agents/phase3-wave1-platform.d.ts +1 -3
  146. package/dist/coordination/agents/phase3-wave2-meta.d.ts +1 -3
  147. package/dist/coordination/agents/phase8-wave3-products.d.ts +1 -3
  148. package/dist/coordination/agents/phase9-wave4-platform-meta.d.ts +1 -3
  149. package/dist/coordination/agents/project-agent-identity.d.ts +11 -11
  150. package/dist/coordination/agents/types.d.ts +13 -2
  151. package/dist/coordination/brain-monitor.d.ts +30 -7
  152. package/dist/coordination/collab-debug.d.ts +1 -1
  153. package/dist/coordination/collab-pause.d.ts +1 -1
  154. package/dist/coordination/delegate-tool.d.ts +7 -5
  155. package/dist/coordination/director-kanban-queue-helpers.d.ts +1 -0
  156. package/dist/coordination/director-options.d.ts +7 -0
  157. package/dist/coordination/director-spawn-model.d.ts +17 -0
  158. package/dist/coordination/director-tools.d.ts +2 -2
  159. package/dist/coordination/director.d.ts +12 -0
  160. package/dist/coordination/dispatcher.d.ts +8 -0
  161. package/dist/coordination/fleet-bus.d.ts +35 -4
  162. package/dist/coordination/fleet-supervisor.d.ts +10 -2
  163. package/dist/coordination/fleet.d.ts +16 -7
  164. package/dist/coordination/index.d.ts +4 -0
  165. package/dist/coordination/index.js +402 -34324
  166. package/dist/coordination/kanban-dispatch-port.d.ts +2 -2
  167. package/dist/coordination/mailbox-http-router.d.ts +4 -3
  168. package/dist/coordination/mailbox-http-validation.d.ts +1 -1
  169. package/dist/coordination/mailbox-project-server.js +48 -3276
  170. package/dist/coordination/mailbox-types.d.ts +24 -6
  171. package/dist/coordination/model-tier-leader.d.ts +122 -0
  172. package/dist/coordination/model-tier.d.ts +122 -0
  173. package/dist/coordination/multi-agent-coordinator.d.ts +24 -0
  174. package/dist/coordination/origin-session.d.ts +16 -0
  175. package/dist/coordination/provider-status-tracker.d.ts +41 -5
  176. package/dist/coordination/session-note-hub.d.ts +44 -0
  177. package/dist/coordination/session-note-tool.d.ts +10 -0
  178. package/dist/core/agent.d.ts +15 -2
  179. package/dist/core/context.d.ts +22 -3
  180. package/dist/core/index.d.ts +2 -0
  181. package/dist/core/index.js +239 -12360
  182. package/dist/core/model-ref.js +5 -23
  183. package/dist/core/provider-runner.d.ts +36 -1
  184. package/dist/core/request-conversation-binding.d.ts +37 -0
  185. package/dist/core/session-notes.d.ts +25 -0
  186. package/dist/core/system-prompt-builder.d.ts +11 -2
  187. package/dist/core/system-prompt-variants.d.ts +78 -0
  188. package/dist/design/index.js +50 -1087
  189. package/dist/execution/auto-compaction-middleware.d.ts +11 -0
  190. package/dist/execution/autonomy-brain.d.ts +2 -1
  191. package/dist/execution/autonomy-prompt-contributor.d.ts +7 -2
  192. package/dist/execution/compaction-core.d.ts +4 -4
  193. package/dist/execution/eternal-autonomy.d.ts +1 -1
  194. package/dist/execution/index.d.ts +1 -1
  195. package/dist/execution/index.js +1615 -21895
  196. package/dist/execution/model-runtime.d.ts +0 -7
  197. package/dist/execution/parallel-eternal-engine.d.ts +7 -7
  198. package/dist/execution/prompt-enhancer.js +16 -535
  199. package/dist/execution/retry-policy.d.ts +31 -1
  200. package/dist/extension/index.js +7 -311
  201. package/dist/goal/index.js +33 -3435
  202. package/dist/hooks/index.js +14 -1138
  203. package/dist/hq/index.js +114 -5139
  204. package/dist/hq/persistence/event-log.d.ts +0 -1
  205. package/dist/hq/persistence.d.ts +4 -4
  206. package/dist/hq/protocol.js +21 -668
  207. package/dist/hq/publisher.d.ts +2 -0
  208. package/dist/index.d.ts +14 -11
  209. package/dist/index.js +1502 -92655
  210. package/dist/infrastructure/index.d.ts +4 -4
  211. package/dist/infrastructure/index.js +504 -5230
  212. package/dist/infrastructure/token-counter.d.ts +9 -0
  213. package/dist/kernel/events/agent-events.d.ts +77 -3
  214. package/dist/kernel/events/brain-events.d.ts +12 -0
  215. package/dist/kernel/events/provider-events.d.ts +22 -1
  216. package/dist/kernel/events/session-events.d.ts +14 -0
  217. package/dist/kernel/events/tool-events.d.ts +13 -0
  218. package/dist/kernel/index.js +14 -992
  219. package/dist/mailbox-attach.d.ts +1 -1
  220. package/dist/models/index.js +41 -1117
  221. package/dist/notifications/index.js +3 -108
  222. package/dist/observability/event-bridge.d.ts +25 -5
  223. package/dist/observability/index.d.ts +9 -8
  224. package/dist/observability/index.js +477 -631
  225. package/dist/observability/tool-usage-source.d.ts +65 -0
  226. package/dist/plugin/index.d.ts +5 -0
  227. package/dist/plugin/index.js +600 -14835
  228. package/dist/plugins/auto-review-plugin.d.ts +3 -2
  229. package/dist/plugins/review-finding-commands.d.ts +0 -8
  230. package/dist/plugins/review-finding-store.js +22 -0
  231. package/dist/plugins/review-finding-types.js +19 -0
  232. package/dist/plugins/review-report-integration.js +21 -0
  233. package/dist/plugins/review-report-store.js +20 -0
  234. package/dist/plugins/review-report-types.js +13 -0
  235. package/dist/prompts/index.js +14 -948
  236. package/dist/registry/index.js +9 -822
  237. package/dist/registry/tool-registry.d.ts +59 -9
  238. package/dist/registry-7N4O3E7W.js +14 -0
  239. package/dist/replay/index.js +6 -84
  240. package/dist/security/capabilities.d.ts +6 -0
  241. package/dist/security/file-permissions.d.ts +1 -1
  242. package/dist/security/index.js +161 -2235
  243. package/dist/security/permission-policy.d.ts +12 -1
  244. package/dist/security/secret-scrubber.d.ts +15 -0
  245. package/dist/session-catalog/index.d.ts +1 -0
  246. package/dist/session-catalog/index.js +41 -2103
  247. package/dist/session-catalog/project-server.js +59 -1435
  248. package/dist/session-catalog/protocol.d.ts +32 -1
  249. package/dist/session-catalog/registry.d.ts +17 -1
  250. package/dist/session-catalog/session-agents.d.ts +56 -0
  251. package/dist/session-catalog/store-rebuild.d.ts +14 -0
  252. package/dist/session-catalog/store-schema.d.ts +26 -0
  253. package/dist/session-catalog/store.d.ts +27 -1
  254. package/dist/session-note-attach.d.ts +9 -0
  255. package/dist/skills/index.js +35 -1578
  256. package/dist/statusline/index.d.ts +43 -0
  257. package/dist/statusline/index.js +102 -0
  258. package/dist/storage/board-store-port.d.ts +2 -2
  259. package/dist/storage/config-loader/in-project-policy.d.ts +29 -0
  260. package/dist/storage/config-loader.d.ts +3 -2
  261. package/dist/storage/file-session-writer.d.ts +4 -2
  262. package/dist/storage/goal-coordination.d.ts +6 -1
  263. package/dist/storage/goal-kanban.d.ts +4 -1
  264. package/dist/storage/index.d.ts +12 -4
  265. package/dist/storage/index.js +198 -15601
  266. package/dist/storage/input-history-store.d.ts +2 -4
  267. package/dist/storage/memory-curator.d.ts +133 -0
  268. package/dist/storage/session-agent-attribution.d.ts +39 -0
  269. package/dist/storage/session-doctor.d.ts +109 -0
  270. package/dist/storage/session-event-bridge.d.ts +5 -7
  271. package/dist/storage/session-outcome.d.ts +42 -0
  272. package/dist/storage/session-recovery.d.ts +55 -0
  273. package/dist/storage/session-store/load-session-data.d.ts +11 -0
  274. package/dist/storage/session-store/prune-helpers.d.ts +1 -1
  275. package/dist/storage/session-store/resume-session.d.ts +0 -2
  276. package/dist/storage/session-store/types.d.ts +8 -0
  277. package/dist/storage/session-store.d.ts +3 -3
  278. package/dist/storage/session-summary-tracker.d.ts +8 -1
  279. package/dist/storage/session-write-buffer.d.ts +18 -0
  280. package/dist/storage/session-writer-truncate.d.ts +2 -0
  281. package/dist/tasking/index.js +10 -676
  282. package/dist/tools/fallback-manage-tools.d.ts +6 -4
  283. package/dist/tools/index.js +52 -9590
  284. package/dist/tools/model-tier-set-tool.d.ts +66 -0
  285. package/dist/types/agent-bridge.d.ts +2 -1
  286. package/dist/types/config/autonomy.d.ts +5 -0
  287. package/dist/types/config/mcp-features.d.ts +7 -0
  288. package/dist/types/config/model-tiers.d.ts +148 -0
  289. package/dist/types/config/root.d.ts +9 -0
  290. package/dist/types/config/tools.d.ts +73 -0
  291. package/dist/types/config/ui.d.ts +1 -1
  292. package/dist/types/config.d.ts +1 -0
  293. package/dist/types/context.d.ts +2 -0
  294. package/dist/types/default-config.d.ts +6 -0
  295. package/dist/types/errors.d.ts +20 -1
  296. package/dist/types/index.d.ts +11 -9
  297. package/dist/types/index.js +131 -1342
  298. package/dist/types/multi-agent.d.ts +43 -0
  299. package/dist/types/plugin.d.ts +1 -1
  300. package/dist/types/provider-runner.d.ts +8 -0
  301. package/dist/types/provider.d.ts +15 -0
  302. package/dist/types/runtime-capability-manifest.d.ts +1 -1
  303. package/dist/types/secret-scrubber.d.ts +15 -0
  304. package/dist/types/session-markers.d.ts +37 -10
  305. package/dist/types/session-markers.js +8 -144
  306. package/dist/types/session-timeline.d.ts +197 -0
  307. package/dist/types/session-timeline.js +12 -0
  308. package/dist/types/session.d.ts +172 -11
  309. package/dist/types/system-prompt.d.ts +27 -0
  310. package/dist/utils/config-backup.d.ts +9 -1
  311. package/dist/utils/error.js +3 -4
  312. package/dist/utils/expect-defined.js +3 -9
  313. package/dist/utils/heap-watchdog.js +6 -706
  314. package/dist/utils/index.js +271 -5685
  315. package/dist/utils/message-invariants.d.ts +4 -1
  316. package/dist/utils/regex-guard.d.ts +1 -1
  317. package/dist/utils/sage-output-block.js +5 -42
  318. package/dist/utils/socket-path.d.ts +1 -1
  319. package/dist/utils/token-estimate.d.ts +6 -2
  320. package/dist/utils/wstack-paths.d.ts +2 -0
  321. package/dist/wiring/proxy-rewrite.d.ts +46 -0
  322. package/dist/wiring/proxy-rewrite.js +24 -121
  323. package/dist/worktree/index.js +6 -887
  324. package/instructions/agents/explore-companion.md +45 -18
  325. package/instructions/agents/memory-curator.md +34 -22
  326. package/instructions/coordination/director-preamble.md +12 -7
  327. package/instructions/coordination/subagent-baseline.md +26 -5
  328. package/instructions/leader-after-task.md +4 -1
  329. package/instructions/llm/chimera-review.md +30 -9
  330. package/instructions/llm/memory-curator.md +31 -0
  331. package/instructions/sections/tool/delegation-compact.md +2 -0
  332. package/instructions/sections/tool/delegation-full.md +5 -1
  333. package/instructions/sections/tool/mailbox-compact.md +5 -1
  334. package/instructions/sections/tool/mailbox-full.md +3 -0
  335. package/instructions/sections/tool/session-note-compact.md +3 -0
  336. package/instructions/sections/tool/session-note-full.md +15 -0
  337. package/instructions/system-lite.md +9 -4
  338. package/instructions/system-pro.md +66 -143
  339. package/instructions/system.md +73 -175
  340. package/package.json +32 -4
  341. package/dist/coordination/agents.d.ts +0 -8
  342. package/dist/coordination/transport.d.ts +0 -2
  343. package/dist/storage/session-writer/session-writer-summary-tracker.d.ts +0 -28
@@ -1,3 +1,161 @@
1
+ import {
2
+ emitProcessCompleted,
3
+ emitProcessOutput,
4
+ emitProcessStarted,
5
+ redactCommand,
6
+ redactCommandArgs,
7
+ runWithProcessTelemetry
8
+ } from "../chunk-NDBIPFNY.js";
9
+ import {
10
+ runWithNetworkTelemetry,
11
+ startNetworkTelemetryMonitor
12
+ } from "../chunk-HA4ZYA6T.js";
13
+ import "../chunk-PFZO3C6H.js";
14
+ import {
15
+ toErrorMessage
16
+ } from "../chunk-TBRL2RCC.js";
17
+
18
+ // src/observability/event-bridge.ts
19
+ function wireMetricsToEvents(events, sink) {
20
+ const unsubs = [];
21
+ const toolUsage = /* @__PURE__ */ new Map();
22
+ const record = (name, ok, durationMs) => {
23
+ const metricName = metricToolName(name);
24
+ const now = Date.now();
25
+ const existing = toolUsage.get(metricName);
26
+ if (existing) {
27
+ existing.invocations += 1;
28
+ if (!ok) existing.failures += 1;
29
+ existing.durationMsTotal += durationMs;
30
+ existing.lastInvokedAt = now;
31
+ } else {
32
+ toolUsage.set(metricName, {
33
+ invocations: 1,
34
+ failures: ok ? 0 : 1,
35
+ durationMsTotal: durationMs,
36
+ lastInvokedAt: now,
37
+ firstInvokedAt: now
38
+ });
39
+ }
40
+ };
41
+ unsubs.push(
42
+ events.on("session.started", () => sink.counter("agent.sessions.started")),
43
+ events.on("session.ended", (e) => {
44
+ sink.counter("agent.sessions.ended");
45
+ sink.histogram("agent.session.tokens.input", e.usage.input);
46
+ sink.histogram("agent.session.tokens.output", e.usage.output);
47
+ }),
48
+ events.on("session.damaged", () => sink.counter("agent.sessions.damaged")),
49
+ events.on("iteration.completed", () => sink.counter("agent.iterations.total")),
50
+ events.on("iteration.limit_reached", () => sink.counter("agent.iteration_limit.hit")),
51
+ events.on("provider.response", (e) => {
52
+ sink.counter("provider.responses.total", 1, { stop_reason: e.stopReason });
53
+ sink.counter("provider.tokens.input", e.usage.input);
54
+ sink.counter("provider.tokens.output", e.usage.output);
55
+ if (e.usage.cacheRead) sink.counter("provider.tokens.cache_read", e.usage.cacheRead);
56
+ if (e.usage.cacheWrite) sink.counter("provider.tokens.cache_write", e.usage.cacheWrite);
57
+ }),
58
+ events.on(
59
+ "provider.retry",
60
+ (e) => sink.counter("provider.retries.total", 1, {
61
+ provider: e.providerId,
62
+ status: String(e.status)
63
+ })
64
+ ),
65
+ events.on(
66
+ "provider.error",
67
+ (e) => sink.counter("provider.errors.total", 1, {
68
+ provider: e.providerId,
69
+ status: String(e.status),
70
+ retryable: String(e.retryable)
71
+ })
72
+ ),
73
+ events.on("tool.started", (e) => {
74
+ sink.counter("tool.starts.total", 1, { tool: metricToolName(e.name) });
75
+ record(e.name, true, 0);
76
+ }),
77
+ events.on("tool.executed", (e) => {
78
+ const tool = metricToolName(e.name);
79
+ sink.counter("tool.executions.total", 1, { tool, ok: String(e.ok) });
80
+ sink.histogram("tool.duration_ms", e.durationMs, { tool });
81
+ record(e.name, e.ok, e.durationMs);
82
+ }),
83
+ events.on("token.threshold", (e) => sink.gauge("agent.tokens.used", e.used)),
84
+ events.on("compaction.fired", (e) => {
85
+ sink.counter("compaction.fired.total");
86
+ sink.histogram("compaction.reduction_tokens", e.report.before - e.report.after);
87
+ }),
88
+ // MCP server names are user-controlled and may contain tenant/project
89
+ // identifiers. Keep them out of metric labels to bound cardinality and
90
+ // prevent operational metadata leaks.
91
+ events.on("mcp.server.connected", () => sink.counter("mcp.connects.total")),
92
+ events.on("mcp.server.reconnected", () => sink.counter("mcp.reconnects.total")),
93
+ events.on("mcp.server.disconnected", () => sink.counter("mcp.disconnects.total")),
94
+ events.on("error", (e) => sink.counter("agent.errors.total", 1, { phase: e.phase }))
95
+ );
96
+ return {
97
+ sink,
98
+ getToolUsage() {
99
+ return toolUsage;
100
+ },
101
+ dispose() {
102
+ for (const u of unsubs) u();
103
+ }
104
+ };
105
+ }
106
+ function metricToolName(name) {
107
+ return name.startsWith("mcp__") ? "mcp_proxy" : name;
108
+ }
109
+
110
+ // src/observability/health.ts
111
+ var SEVERITY = {
112
+ healthy: 0,
113
+ degraded: 1,
114
+ unhealthy: 2
115
+ };
116
+ var DefaultHealthRegistry = class {
117
+ checks = /* @__PURE__ */ new Map();
118
+ timeoutMs;
119
+ constructor(opts = {}) {
120
+ this.timeoutMs = opts.timeoutMs ?? 5e3;
121
+ }
122
+ register(check) {
123
+ this.checks.set(check.name, check);
124
+ }
125
+ unregister(name) {
126
+ this.checks.delete(name);
127
+ }
128
+ async run() {
129
+ const results = await Promise.all(
130
+ Array.from(this.checks.values()).map(async (c) => {
131
+ const result = await this.runOne(c);
132
+ return { name: c.name, ...result };
133
+ })
134
+ );
135
+ let status = "healthy";
136
+ for (const r of results) {
137
+ if (SEVERITY[r.status] > SEVERITY[status]) status = r.status;
138
+ }
139
+ return { status, timestamp: Date.now(), checks: results };
140
+ }
141
+ async runOne(check) {
142
+ let timer = null;
143
+ const timeout = new Promise((resolve) => {
144
+ timer = setTimeout(
145
+ () => resolve({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
146
+ this.timeoutMs
147
+ );
148
+ });
149
+ try {
150
+ return await Promise.race([check.check(), timeout]);
151
+ } catch (err) {
152
+ return { status: "unhealthy", detail: toErrorMessage(err) };
153
+ } finally {
154
+ if (timer) clearTimeout(timer);
155
+ }
156
+ }
157
+ };
158
+
1
159
  // src/observability/metrics.ts
2
160
  var RESERVOIR_SIZE = 1024;
3
161
  function labelKey(labels) {
@@ -168,638 +326,61 @@ var InMemoryMetricsSink = class {
168
326
  recordDrop(name) {
169
327
  this.droppedByMetric.set(name, (this.droppedByMetric.get(name) ?? 0) + 1);
170
328
  }
171
- };
172
- function parseLabelKey(key) {
173
- if (!key) return {};
174
- const labels = {};
175
- for (const pair of key.split(",")) {
176
- const eq = pair.indexOf("=");
177
- if (eq > 0) labels[pair.slice(0, eq)] = pair.slice(eq + 1);
178
- }
179
- return labels;
180
- }
181
- var NoopMetricsSink = class {
182
- counter() {
183
- }
184
- gauge() {
185
- }
186
- histogram() {
187
- }
188
- snapshot() {
189
- return { timestamp: Date.now(), series: [] };
190
- }
191
- droppedObservations() {
192
- return 0;
193
- }
194
- reset() {
195
- }
196
- };
197
-
198
- // src/utils/error.ts
199
- function toErrorMessage(err) {
200
- return err instanceof Error ? err.message : String(err);
201
- }
202
-
203
- // src/observability/health.ts
204
- var SEVERITY = {
205
- healthy: 0,
206
- degraded: 1,
207
- unhealthy: 2
208
- };
209
- var DefaultHealthRegistry = class {
210
- checks = /* @__PURE__ */ new Map();
211
- timeoutMs;
212
- constructor(opts = {}) {
213
- this.timeoutMs = opts.timeoutMs ?? 5e3;
214
- }
215
- register(check) {
216
- this.checks.set(check.name, check);
217
- }
218
- unregister(name) {
219
- this.checks.delete(name);
220
- }
221
- async run() {
222
- const results = await Promise.all(
223
- Array.from(this.checks.values()).map(async (c) => {
224
- const result = await this.runOne(c);
225
- return { name: c.name, ...result };
226
- })
227
- );
228
- let status = "healthy";
229
- for (const r of results) {
230
- if (SEVERITY[r.status] > SEVERITY[status]) status = r.status;
231
- }
232
- return { status, timestamp: Date.now(), checks: results };
233
- }
234
- async runOne(check) {
235
- let timer = null;
236
- const timeout = new Promise((resolve) => {
237
- timer = setTimeout(
238
- () => resolve({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
239
- this.timeoutMs
240
- );
241
- });
242
- try {
243
- return await Promise.race([check.check(), timeout]);
244
- } catch (err) {
245
- return { status: "unhealthy", detail: toErrorMessage(err) };
246
- } finally {
247
- if (timer) clearTimeout(timer);
248
- }
249
- }
250
- };
251
-
252
- // src/observability/tracer.ts
253
- var NoopTracer = class {
254
- startSpan() {
255
- return NOOP_SPAN;
256
- }
257
- };
258
- var NOOP_SPAN = {
259
- setAttribute() {
260
- },
261
- recordError() {
262
- },
263
- end() {
264
- }
265
- };
266
-
267
- // src/observability/otel-tracer.ts
268
- var OTEL_STATUS_ERROR = 2;
269
- var OTelTracer = class {
270
- constructor(upstream) {
271
- this.upstream = upstream;
272
- }
273
- upstream;
274
- startSpan(name, attrs) {
275
- const otelSpan = this.upstream.startSpan(name, attrs ? { attributes: attrs } : void 0);
276
- return new OTelSpan(otelSpan);
277
- }
278
- };
279
- var OTelSpan = class {
280
- constructor(span) {
281
- this.span = span;
282
- }
283
- span;
284
- setAttribute(key, value) {
285
- this.span.setAttribute(key, value);
286
- }
287
- recordError(err) {
288
- this.span.recordException(err);
289
- this.span.setStatus?.({ code: OTEL_STATUS_ERROR, message: err.message });
290
- }
291
- end() {
292
- this.span.end();
293
- }
294
- };
295
-
296
- // src/observability/event-bridge.ts
297
- function wireMetricsToEvents(events, sink) {
298
- const unsubs = [];
299
- unsubs.push(
300
- events.on("session.started", () => sink.counter("agent.sessions.started")),
301
- events.on("session.ended", (e) => {
302
- sink.counter("agent.sessions.ended");
303
- sink.histogram("agent.session.tokens.input", e.usage.input);
304
- sink.histogram("agent.session.tokens.output", e.usage.output);
305
- }),
306
- events.on("session.damaged", () => sink.counter("agent.sessions.damaged")),
307
- events.on("iteration.completed", () => sink.counter("agent.iterations.total")),
308
- events.on("iteration.limit_reached", () => sink.counter("agent.iteration_limit.hit")),
309
- events.on("provider.response", (e) => {
310
- sink.counter("provider.responses.total", 1, { stop_reason: e.stopReason });
311
- sink.counter("provider.tokens.input", e.usage.input);
312
- sink.counter("provider.tokens.output", e.usage.output);
313
- if (e.usage.cacheRead) sink.counter("provider.tokens.cache_read", e.usage.cacheRead);
314
- if (e.usage.cacheWrite) sink.counter("provider.tokens.cache_write", e.usage.cacheWrite);
315
- }),
316
- events.on(
317
- "provider.retry",
318
- (e) => sink.counter("provider.retries.total", 1, {
319
- provider: e.providerId,
320
- status: String(e.status)
321
- })
322
- ),
323
- events.on(
324
- "provider.error",
325
- (e) => sink.counter("provider.errors.total", 1, {
326
- provider: e.providerId,
327
- status: String(e.status),
328
- retryable: String(e.retryable)
329
- })
330
- ),
331
- events.on(
332
- "tool.started",
333
- (e) => sink.counter("tool.starts.total", 1, { tool: metricToolName(e.name) })
334
- ),
335
- events.on("tool.executed", (e) => {
336
- const tool = metricToolName(e.name);
337
- sink.counter("tool.executions.total", 1, { tool, ok: String(e.ok) });
338
- sink.histogram("tool.duration_ms", e.durationMs, { tool });
339
- }),
340
- events.on("token.threshold", (e) => sink.gauge("agent.tokens.used", e.used)),
341
- events.on("compaction.fired", (e) => {
342
- sink.counter("compaction.fired.total");
343
- sink.histogram("compaction.reduction_tokens", e.report.before - e.report.after);
344
- }),
345
- // MCP server names are user-controlled and may contain tenant/project
346
- // identifiers. Keep them out of metric labels to bound cardinality and
347
- // prevent operational metadata leaks.
348
- events.on("mcp.server.connected", () => sink.counter("mcp.connects.total")),
349
- events.on("mcp.server.reconnected", () => sink.counter("mcp.reconnects.total")),
350
- events.on("mcp.server.disconnected", () => sink.counter("mcp.disconnects.total")),
351
- events.on("error", (e) => sink.counter("agent.errors.total", 1, { phase: e.phase }))
352
- );
353
- return () => {
354
- for (const u of unsubs) u();
355
- };
356
- }
357
- function metricToolName(name) {
358
- return name.startsWith("mcp__") ? "mcp_proxy" : name;
359
- }
360
-
361
- // src/observability/process-telemetry.ts
362
- import { AsyncLocalStorage } from "node:async_hooks";
363
- import { createHash } from "node:crypto";
364
-
365
- // src/utils/expect-defined.ts
366
- function expectDefined(value, label) {
367
- if (value === null || value === void 0) {
368
- const err = new Error(label ? `Expected ${label} to be defined` : "Expected value to be defined");
369
- err.name = "ExpectDefinedError";
370
- throw err;
371
- }
372
- return value;
373
- }
374
-
375
- // src/observability/redact-command.ts
376
- var SENSITIVE_FLAG_PATTERNS = [
377
- // --flag=value or --flag "value" (value captured up to next space or comma)
378
- /--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\s,][^\s]*)?/gi,
379
- // -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.
380
- // The separator group is optional so the attached form (the common one) matches.
381
- // (?<![-\w]) anchors to a token start so we don't match the `-t` inside `--token`.
382
- // The value must be token-like (>= 8 chars) so ordinary combined flags such
383
- // as `tar -tf` / `ssh -tt` are not eaten. Global flag: EVERY occurrence is
384
- // redacted, not just the first. Synced with packages/tools _redact-command.ts.
385
- /(?<![-\w])-t(?:[=\s]+)?[^\s,-]{8,}/g,
386
- // -p|-password|-a (redis auth) short flags: attached + separated + =value.
387
- // Same token-start anchor; over-redaction is an accepted tradeoff for a
388
- // redaction function (false positive = cosmetic noise; false negative = leak).
389
- /(?<![-\w])-(?:password|p|a)(?:[=\s]+)?[^\s,-]+/gi,
390
- // env var–style secrets: TOKEN=x, API_KEY=y, etc.
391
- /(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD|PASSPHRASE)\s*[=:]\s*[^\s,]+/gi,
392
- // Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
393
- // when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
394
- // every such flag in the command line is redacted, not just the first.
395
- /--\w*(?:token|key|secret|password|passwd|auth|credential)\w*[=\s,][A-Za-z0-9+/=]{32,}/g
396
- ];
397
- function redactCommand(cmd) {
398
- let result = cmd;
399
- for (const pattern of SENSITIVE_FLAG_PATTERNS) {
400
- result = result.replace(pattern, (match) => {
401
- const eq = match.indexOf("=");
402
- const sp = match.search(/\s/);
403
- const delim = eq !== -1 ? "=" : sp !== -1 ? match[sp] : null;
404
- if (delim !== null) {
405
- const flag = match.slice(0, match.indexOf(expectDefined(delim)) + 1);
406
- return `${flag}[REDACTED]`;
407
- }
408
- if (match.startsWith("--")) {
409
- return match;
410
- }
411
- return `${match.slice(0, 2)}[REDACTED]`;
412
- });
413
- }
414
- return result;
415
- }
416
- var BARE_SENSITIVE_LONG_FLAG = /^--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)$/i;
417
- var BARE_SENSITIVE_SHORT_FLAG = /^-(?:p|t|a)$/i;
418
- function redactCommandArgs(command, args) {
419
- const redactedCommand = redactCommand(command);
420
- const redactedArgs = args.map((a) => redactCommand(a));
421
- for (let i = 0; i < redactedArgs.length - 1; i++) {
422
- const flag = redactedArgs[i];
423
- const next = redactedArgs[i + 1];
424
- if (flag === void 0 || next === void 0) continue;
425
- const isBareSensitive = (BARE_SENSITIVE_LONG_FLAG.test(flag) || BARE_SENSITIVE_SHORT_FLAG.test(flag)) && !flag.includes("[REDACTED]");
426
- if (isBareSensitive && !next.startsWith("-")) {
427
- redactedArgs[i + 1] = "[REDACTED]";
428
- }
429
- }
430
- return { command: redactedCommand, args: redactedArgs };
431
- }
432
-
433
- // src/observability/process-telemetry.ts
434
- var storage = new AsyncLocalStorage();
435
- function runWithProcessTelemetry(context, run) {
436
- return storage.run(context, run);
437
- }
438
- function emitProcessStarted(event) {
439
- const { command, args } = redactCommandArgs(event.command, event.args);
440
- emit("process.started", { ...event, command, args });
441
- }
442
- function emitProcessOutput(input) {
443
- const bytes = Buffer.byteLength(input.chunk);
444
- emit("process.output", {
445
- ...input.pid !== void 0 ? { pid: input.pid } : {},
446
- stream: input.stream,
447
- bytes,
448
- chunkHash: createHash("sha256").update(input.chunk).digest("hex"),
449
- at: (/* @__PURE__ */ new Date()).toISOString()
450
- });
451
- }
452
- function emitProcessCompleted(event) {
453
- emit("process.completed", event);
454
- }
455
- function emit(name, event) {
456
- const context = storage.getStore();
457
- if (!context) return;
458
- const { events, ...identity2 } = context;
459
- events.emit(name, { ...identity2, ...event });
460
- }
461
-
462
- // src/observability/network-telemetry.ts
463
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
464
- import { createHash as createHash2, randomUUID } from "node:crypto";
465
- import { channel } from "node:diagnostics_channel";
466
- var storage2 = new AsyncLocalStorage2();
467
- var requests = /* @__PURE__ */ new WeakMap();
468
- var users = 0;
469
- var unsubscribe;
470
- function runWithNetworkTelemetry(context, run) {
471
- return storage2.run(context, run);
472
- }
473
- function startNetworkTelemetryMonitor() {
474
- users++;
475
- if (!unsubscribe) unsubscribe = subscribe();
476
- let stopped = false;
477
- return () => {
478
- if (stopped) return;
479
- stopped = true;
480
- users--;
481
- if (users <= 0) {
482
- unsubscribe?.();
483
- unsubscribe = void 0;
484
- users = 0;
485
- }
486
- };
487
- }
488
- function subscribe() {
489
- const create = channel("undici:request:create");
490
- const headers = channel("undici:request:headers");
491
- const error = channel("undici:request:error");
492
- const onCreate = (message) => {
493
- const context = storage2.getStore();
494
- const request = objectField(message, "request");
495
- if (!context || !request) return;
496
- const target = safeTarget(request);
497
- const startedAt = (/* @__PURE__ */ new Date()).toISOString();
498
- const requestBytes = numberField(request, "contentLength");
499
- const state = {
500
- ...context,
501
- requestId: randomUUID(),
502
- ...target,
503
- ...requestBytes !== void 0 ? { requestBytes } : {},
504
- startedAt,
505
- startedNs: process.hrtime.bigint()
506
- };
507
- requests.set(request, state);
508
- context.events.emit("network.request.started", {
509
- ...identity(state),
510
- requestId: state.requestId,
511
- method: state.method,
512
- scheme: state.scheme,
513
- serverAddress: state.serverAddress,
514
- ...state.serverPort !== void 0 ? { serverPort: state.serverPort } : {},
515
- pathHash: state.pathHash,
516
- queryKeys: queryKeys(request),
517
- ...state.requestBytes !== void 0 ? { requestBytes: state.requestBytes } : {},
518
- startedAt
519
- });
520
- };
521
- const onHeaders = (message) => {
522
- const request = objectField(message, "request");
523
- if (!request) return;
524
- const state = requests.get(request);
525
- if (!state) return;
526
- requests.delete(request);
527
- const response = objectField(message, "response");
528
- const responseBytes = contentLength(response);
529
- state.events.emit("network.request.completed", {
530
- ...identity(state),
531
- requestId: state.requestId,
532
- method: state.method,
533
- scheme: state.scheme,
534
- serverAddress: state.serverAddress,
535
- ...state.serverPort !== void 0 ? { serverPort: state.serverPort } : {},
536
- pathHash: state.pathHash,
537
- statusCode: numberField(response, "statusCode") ?? 0,
538
- durationMs: elapsed(state.startedNs),
539
- ...state.requestBytes !== void 0 ? { requestBytes: state.requestBytes } : {},
540
- ...responseBytes !== void 0 ? { responseBytes } : {},
541
- completedAt: (/* @__PURE__ */ new Date()).toISOString()
542
- });
543
- };
544
- const onError = (message) => {
545
- const request = objectField(message, "request");
546
- if (!request) return;
547
- const state = requests.get(request);
548
- if (!state) return;
549
- requests.delete(request);
550
- const err = objectField(message, "error");
551
- const errorCode = stringField(err, "code");
552
- state.events.emit("network.request.failed", {
553
- ...identity(state),
554
- requestId: state.requestId,
555
- method: state.method,
556
- scheme: state.scheme,
557
- serverAddress: state.serverAddress,
558
- ...state.serverPort !== void 0 ? { serverPort: state.serverPort } : {},
559
- pathHash: state.pathHash,
560
- durationMs: elapsed(state.startedNs),
561
- errorName: stringField(err, "name") ?? "Error",
562
- ...errorCode ? { errorCode } : {},
563
- failedAt: (/* @__PURE__ */ new Date()).toISOString()
564
- });
565
- };
566
- create.subscribe(onCreate);
567
- headers.subscribe(onHeaders);
568
- error.subscribe(onError);
569
- return () => {
570
- create.unsubscribe(onCreate);
571
- headers.unsubscribe(onHeaders);
572
- error.unsubscribe(onError);
573
- };
574
- }
575
- function identity(state) {
576
- return {
577
- sessionId: state.sessionId,
578
- ...state.traceId ? { traceId: state.traceId } : {},
579
- ...state.agentId ? { agentId: state.agentId } : {},
580
- ...state.toolCallId ? { toolCallId: state.toolCallId } : {},
581
- ...state.attemptId ? { attemptId: state.attemptId } : {},
582
- initiator: state.initiator,
583
- operationName: state.operationName
584
- };
585
- }
586
- function safeTarget(request) {
587
- const origin = String(request.origin ?? "");
588
- const path = String(request.path ?? "");
589
- let url;
590
- try {
591
- url = new URL(path, origin);
592
- } catch {
593
- url = new URL("http://invalid.local/");
594
- }
595
- return {
596
- method: String(request.method ?? "GET").toUpperCase(),
597
- scheme: url.protocol.replace(":", ""),
598
- serverAddress: url.hostname,
599
- ...url.port ? { serverPort: Number(url.port) } : {},
600
- pathHash: hash(url.pathname)
601
- };
602
- }
603
- function queryKeys(request) {
604
- try {
605
- return [...new URL(String(request.path ?? ""), String(request.origin ?? "")).searchParams.keys()].filter((v, i, a) => a.indexOf(v) === i).slice(0, 50);
606
- } catch {
607
- return [];
608
- }
609
- }
610
- function contentLength(response) {
611
- const raw = response?.headers;
612
- if (!Array.isArray(raw)) return void 0;
613
- for (let index = 0; index < raw.length - 1; index += 2) if (String(raw[index]).toLowerCase() === "content-length") {
614
- const value = Number(String(raw[index + 1]));
615
- return Number.isFinite(value) ? value : void 0;
616
- }
617
- return void 0;
618
- }
619
- function objectField(value, key) {
620
- return value && typeof value === "object" && value[key] && typeof value[key] === "object" ? value[key] : void 0;
621
- }
622
- function stringField(value, key) {
623
- return typeof value?.[key] === "string" ? value[key] : void 0;
624
- }
625
- function numberField(value, key) {
626
- return typeof value?.[key] === "number" ? value[key] : void 0;
627
- }
628
- function elapsed(start) {
629
- return Number(process.hrtime.bigint() - start) / 1e6;
630
- }
631
- function hash(value) {
632
- return createHash2("sha256").update(value).digest("hex");
633
- }
634
-
635
- // src/observability/prometheus.ts
636
- import { timingSafeEqual } from "node:crypto";
637
- var NUMBER_FORMAT_INFINITY = "NaN";
638
- function escapeLabelValue(v) {
639
- return v.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/"/g, '\\"');
640
- }
641
- function formatLabels(labels) {
642
- const keys = Object.keys(labels);
643
- if (keys.length === 0) return "";
644
- const parts = keys.map((k) => `${k}="${escapeLabelValue(labels[k] ?? "")}"`);
645
- return `{${parts.join(",")}}`;
646
- }
647
- function formatNumber(n) {
648
- if (!Number.isFinite(n)) return NUMBER_FORMAT_INFINITY;
649
- return Number.isInteger(n) ? n.toString() : n.toString();
650
- }
651
- function joinLabels(base, extra) {
652
- return { ...base, ...extra };
653
- }
654
- function renderPrometheus(snapshot) {
655
- const groups = /* @__PURE__ */ new Map();
656
- for (const s of snapshot.series) {
657
- let g = groups.get(s.name);
658
- if (!g) {
659
- g = { type: s.type, rows: [] };
660
- groups.set(s.name, g);
661
- }
662
- g.rows.push({ labels: s.labels, values: s.values });
663
- }
664
- const lines = [];
665
- for (const [name, g] of groups) {
666
- const promType = g.type === "histogram" ? "summary" : g.type;
667
- lines.push(`# HELP ${name} ${name}`);
668
- lines.push(`# TYPE ${name} ${promType}`);
669
- if (g.type === "counter" || g.type === "gauge") {
670
- for (const row of g.rows) {
671
- lines.push(`${name}${formatLabels(row.labels)} ${formatNumber(row.values.value ?? 0)}`);
672
- }
673
- } else {
674
- for (const row of g.rows) {
675
- const { count = 0, sum = 0, p50 = 0, p95 = 0, p99 = 0 } = row.values;
676
- lines.push(
677
- `${name}${formatLabels(joinLabels(row.labels, { quantile: "0.5" }))} ${formatNumber(p50)}`
678
- );
679
- lines.push(
680
- `${name}${formatLabels(joinLabels(row.labels, { quantile: "0.95" }))} ${formatNumber(p95)}`
681
- );
682
- lines.push(
683
- `${name}${formatLabels(joinLabels(row.labels, { quantile: "0.99" }))} ${formatNumber(p99)}`
684
- );
685
- lines.push(`${name}_sum${formatLabels(row.labels)} ${formatNumber(sum)}`);
686
- lines.push(`${name}_count${formatLabels(row.labels)} ${formatNumber(count)}`);
687
- }
688
- }
689
- }
690
- return lines.join("\n") + "\n";
691
- }
692
- var PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
693
- function isLoopbackBindHost(host) {
694
- const h = host.trim().toLowerCase().replace(/^\[|\]$/g, "");
695
- return h === "127.0.0.1" || h === "localhost" || h === "::1" || h.startsWith("127.");
696
- }
697
- function bearerMatches(header, token) {
698
- if (!header) return false;
699
- const prefix = "bearer ";
700
- if (!header.toLowerCase().startsWith(prefix)) return false;
701
- const supplied = Buffer.from(header.slice(prefix.length).trim(), "utf8");
702
- const expected = Buffer.from(token, "utf8");
703
- if (supplied.length !== expected.length) return false;
704
- return timingSafeEqual(supplied, expected);
705
- }
706
- async function startMetricsServer(opts) {
707
- const tls = opts.tls;
708
- const useHttps = !!(tls?.cert && tls?.key);
709
- const host = opts.host ?? "127.0.0.1";
710
- const path = opts.path ?? "/metrics";
711
- const healthPath = opts.healthPath ?? "/healthz";
712
- const healthRegistry = opts.healthRegistry;
713
- const token = opts.token;
714
- if (!isLoopbackBindHost(host) && !token) {
715
- throw new Error(
716
- `startMetricsServer: refusing to bind to non-loopback host "${host}" without a token \u2014 pass a token to expose metrics to the network, or bind to 127.0.0.1.`
717
- );
718
- }
719
- const listener = (req, res) => {
720
- if (!req.url || req.method !== "GET") {
721
- res.statusCode = req.url ? 405 : 400;
722
- res.end();
723
- return;
724
- }
725
- if (token && !bearerMatches(req.headers.authorization, token)) {
726
- res.statusCode = 401;
727
- res.setHeader("www-authenticate", "Bearer");
728
- res.setHeader("content-type", "text/plain; charset=utf-8");
729
- res.end("Unauthorized");
730
- return;
731
- }
732
- const url = req.url.split("?")[0];
733
- if (url === path) {
734
- let body;
735
- try {
736
- body = renderPrometheus(opts.sink.snapshot());
737
- } catch (err) {
738
- res.statusCode = 500;
739
- res.setHeader("content-type", "text/plain; charset=utf-8");
740
- res.end(`metrics render failed: ${toErrorMessage(err)}`);
741
- return;
742
- }
743
- res.statusCode = 200;
744
- res.setHeader("content-type", PROMETHEUS_CONTENT_TYPE);
745
- res.end(body);
746
- return;
747
- }
748
- if (healthRegistry && url === healthPath) {
749
- healthRegistry.run().then(
750
- (agg) => {
751
- res.statusCode = agg.status === "unhealthy" ? 503 : 200;
752
- res.setHeader("content-type", "application/json; charset=utf-8");
753
- res.end(JSON.stringify(agg, null, 2));
754
- },
755
- (err) => {
756
- res.statusCode = 500;
757
- res.setHeader("content-type", "text/plain; charset=utf-8");
758
- res.end(`health run failed: ${toErrorMessage(err)}`);
759
- }
760
- );
761
- return;
762
- }
763
- res.statusCode = 404;
764
- res.setHeader("content-type", "text/plain; charset=utf-8");
765
- res.end("Not Found");
766
- };
767
- let server;
768
- if (useHttps && tls) {
769
- const { createServer } = await import("node:https");
770
- const { readFileSync } = await import("node:fs");
771
- server = createServer(
772
- { cert: readFileSync(tls.cert), key: readFileSync(tls.key) },
773
- listener
774
- );
775
- } else {
776
- const { createServer } = await import("node:http");
777
- server = createServer(listener);
778
- }
779
- await new Promise((resolve, reject) => {
780
- const onError = (err) => {
781
- server.off("listening", onListening);
782
- reject(err);
783
- };
784
- const onListening = () => {
785
- server.off("error", onError);
786
- resolve();
787
- };
788
- server.once("error", onError);
789
- server.once("listening", onListening);
790
- server.listen(opts.port, host);
791
- });
792
- const addr = server.address();
793
- const boundPort = typeof addr === "object" && addr ? addr.port : opts.port;
794
- const protocol = useHttps ? "https" : "http";
795
- return {
796
- port: boundPort,
797
- url: `${protocol}://${host}:${boundPort}${path}`,
798
- close: () => new Promise((resolve, reject) => {
799
- server.close((err) => err ? reject(err) : resolve());
800
- })
801
- };
329
+ };
330
+ function parseLabelKey(key) {
331
+ if (!key) return {};
332
+ const labels = {};
333
+ for (const pair of key.split(",")) {
334
+ const eq = pair.indexOf("=");
335
+ if (eq > 0) labels[pair.slice(0, eq)] = pair.slice(eq + 1);
336
+ }
337
+ return labels;
802
338
  }
339
+ var NoopMetricsSink = class {
340
+ counter() {
341
+ }
342
+ gauge() {
343
+ }
344
+ histogram() {
345
+ }
346
+ snapshot() {
347
+ return { timestamp: Date.now(), series: [] };
348
+ }
349
+ droppedObservations() {
350
+ return 0;
351
+ }
352
+ reset() {
353
+ }
354
+ };
355
+
356
+ // src/observability/otel-tracer.ts
357
+ var OTEL_STATUS_ERROR = 2;
358
+ var OTelTracer = class {
359
+ constructor(upstream) {
360
+ this.upstream = upstream;
361
+ }
362
+ upstream;
363
+ startSpan(name, attrs) {
364
+ const otelSpan = this.upstream.startSpan(name, attrs ? { attributes: attrs } : void 0);
365
+ return new OTelSpan(otelSpan);
366
+ }
367
+ };
368
+ var OTelSpan = class {
369
+ constructor(span) {
370
+ this.span = span;
371
+ }
372
+ span;
373
+ setAttribute(key, value) {
374
+ this.span.setAttribute(key, value);
375
+ }
376
+ recordError(err) {
377
+ this.span.recordException(err);
378
+ this.span.setStatus?.({ code: OTEL_STATUS_ERROR, message: err.message });
379
+ }
380
+ end() {
381
+ this.span.end();
382
+ }
383
+ };
803
384
 
804
385
  // src/observability/otlp-metrics.ts
805
386
  var DEFAULT_INTERVAL_MS = 3e4;
@@ -1064,6 +645,269 @@ function startOtlpTraceExporter(opts) {
1064
645
  buffered: () => [...buffer]
1065
646
  };
1066
647
  }
648
+
649
+ // src/observability/prometheus.ts
650
+ import { timingSafeEqual } from "node:crypto";
651
+ var NUMBER_FORMAT_INFINITY = "NaN";
652
+ function escapeLabelValue(v) {
653
+ return v.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/"/g, '\\"');
654
+ }
655
+ function formatLabels(labels) {
656
+ const keys = Object.keys(labels);
657
+ if (keys.length === 0) return "";
658
+ const parts = keys.map((k) => `${k}="${escapeLabelValue(labels[k] ?? "")}"`);
659
+ return `{${parts.join(",")}}`;
660
+ }
661
+ function formatNumber(n) {
662
+ if (!Number.isFinite(n)) return NUMBER_FORMAT_INFINITY;
663
+ return Number.isInteger(n) ? n.toString() : n.toString();
664
+ }
665
+ function joinLabels(base, extra) {
666
+ return { ...base, ...extra };
667
+ }
668
+ function renderPrometheus(snapshot) {
669
+ const groups = /* @__PURE__ */ new Map();
670
+ for (const s of snapshot.series) {
671
+ let g = groups.get(s.name);
672
+ if (!g) {
673
+ g = { type: s.type, rows: [] };
674
+ groups.set(s.name, g);
675
+ }
676
+ g.rows.push({ labels: s.labels, values: s.values });
677
+ }
678
+ const lines = [];
679
+ for (const [name, g] of groups) {
680
+ const promType = g.type === "histogram" ? "summary" : g.type;
681
+ lines.push(`# HELP ${name} ${name}`);
682
+ lines.push(`# TYPE ${name} ${promType}`);
683
+ if (g.type === "counter" || g.type === "gauge") {
684
+ for (const row of g.rows) {
685
+ lines.push(`${name}${formatLabels(row.labels)} ${formatNumber(row.values.value ?? 0)}`);
686
+ }
687
+ } else {
688
+ for (const row of g.rows) {
689
+ const { count = 0, sum = 0, p50 = 0, p95 = 0, p99 = 0 } = row.values;
690
+ lines.push(
691
+ `${name}${formatLabels(joinLabels(row.labels, { quantile: "0.5" }))} ${formatNumber(p50)}`
692
+ );
693
+ lines.push(
694
+ `${name}${formatLabels(joinLabels(row.labels, { quantile: "0.95" }))} ${formatNumber(p95)}`
695
+ );
696
+ lines.push(
697
+ `${name}${formatLabels(joinLabels(row.labels, { quantile: "0.99" }))} ${formatNumber(p99)}`
698
+ );
699
+ lines.push(`${name}_sum${formatLabels(row.labels)} ${formatNumber(sum)}`);
700
+ lines.push(`${name}_count${formatLabels(row.labels)} ${formatNumber(count)}`);
701
+ }
702
+ }
703
+ }
704
+ return lines.join("\n") + "\n";
705
+ }
706
+ var PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
707
+ function isLoopbackBindHost(host) {
708
+ const h = host.trim().toLowerCase().replace(/^\[|\]$/g, "");
709
+ return h === "127.0.0.1" || h === "localhost" || h === "::1" || h.startsWith("127.");
710
+ }
711
+ function bearerMatches(header, token) {
712
+ if (!header) return false;
713
+ const prefix = "bearer ";
714
+ if (!header.toLowerCase().startsWith(prefix)) return false;
715
+ const supplied = Buffer.from(header.slice(prefix.length).trim(), "utf8");
716
+ const expected = Buffer.from(token, "utf8");
717
+ if (supplied.length !== expected.length) return false;
718
+ return timingSafeEqual(supplied, expected);
719
+ }
720
+ async function startMetricsServer(opts) {
721
+ const tls = opts.tls;
722
+ const useHttps = !!(tls?.cert && tls?.key);
723
+ const host = opts.host ?? "127.0.0.1";
724
+ const path = opts.path ?? "/metrics";
725
+ const healthPath = opts.healthPath ?? "/healthz";
726
+ const healthRegistry = opts.healthRegistry;
727
+ const token = opts.token;
728
+ if (!isLoopbackBindHost(host) && !token) {
729
+ throw new Error(
730
+ `startMetricsServer: refusing to bind to non-loopback host "${host}" without a token \u2014 pass a token to expose metrics to the network, or bind to 127.0.0.1.`
731
+ );
732
+ }
733
+ const listener = (req, res) => {
734
+ if (!req.url || req.method !== "GET") {
735
+ res.statusCode = req.url ? 405 : 400;
736
+ res.end();
737
+ return;
738
+ }
739
+ if (token && !bearerMatches(req.headers.authorization, token)) {
740
+ res.statusCode = 401;
741
+ res.setHeader("www-authenticate", "Bearer");
742
+ res.setHeader("content-type", "text/plain; charset=utf-8");
743
+ res.end("Unauthorized");
744
+ return;
745
+ }
746
+ const url = req.url.split("?")[0];
747
+ if (url === path) {
748
+ let body;
749
+ try {
750
+ body = renderPrometheus(opts.sink.snapshot());
751
+ } catch (err) {
752
+ res.statusCode = 500;
753
+ res.setHeader("content-type", "text/plain; charset=utf-8");
754
+ res.end(`metrics render failed: ${toErrorMessage(err)}`);
755
+ return;
756
+ }
757
+ res.statusCode = 200;
758
+ res.setHeader("content-type", PROMETHEUS_CONTENT_TYPE);
759
+ res.end(body);
760
+ return;
761
+ }
762
+ if (healthRegistry && url === healthPath) {
763
+ healthRegistry.run().then(
764
+ (agg) => {
765
+ res.statusCode = agg.status === "unhealthy" ? 503 : 200;
766
+ res.setHeader("content-type", "application/json; charset=utf-8");
767
+ res.end(JSON.stringify(agg, null, 2));
768
+ },
769
+ (err) => {
770
+ res.statusCode = 500;
771
+ res.setHeader("content-type", "text/plain; charset=utf-8");
772
+ res.end(`health run failed: ${toErrorMessage(err)}`);
773
+ }
774
+ );
775
+ return;
776
+ }
777
+ res.statusCode = 404;
778
+ res.setHeader("content-type", "text/plain; charset=utf-8");
779
+ res.end("Not Found");
780
+ };
781
+ let server;
782
+ if (useHttps && tls) {
783
+ const { createServer } = await import("node:https");
784
+ const { readFileSync } = await import("node:fs");
785
+ server = createServer(
786
+ { cert: readFileSync(tls.cert), key: readFileSync(tls.key) },
787
+ listener
788
+ );
789
+ } else {
790
+ const { createServer } = await import("node:http");
791
+ server = createServer(listener);
792
+ }
793
+ await new Promise((resolve, reject) => {
794
+ const onError = (err) => {
795
+ server.off("listening", onListening);
796
+ reject(err);
797
+ };
798
+ const onListening = () => {
799
+ server.off("error", onError);
800
+ resolve();
801
+ };
802
+ server.once("error", onError);
803
+ server.once("listening", onListening);
804
+ server.listen(opts.port, host);
805
+ });
806
+ const addr = server.address();
807
+ const boundPort = typeof addr === "object" && addr ? addr.port : opts.port;
808
+ const protocol = useHttps ? "https" : "http";
809
+ return {
810
+ port: boundPort,
811
+ url: `${protocol}://${host}:${boundPort}${path}`,
812
+ close: () => new Promise((resolve, reject) => {
813
+ server.close((err) => err ? reject(err) : resolve());
814
+ })
815
+ };
816
+ }
817
+
818
+ // src/observability/tool-usage-source.ts
819
+ var DEFAULT_LIMIT = 500;
820
+ function daysBetween(later, earlier) {
821
+ return Math.max(0, Math.floor((later - earlier) / 864e5));
822
+ }
823
+ function chronicleCandidates(store, opts, now) {
824
+ const rows = store.underusedTools({
825
+ ...opts.fromDay !== void 0 ? { from: opts.fromDay } : {},
826
+ ...opts.toDay !== void 0 ? { to: opts.toDay } : {},
827
+ limit: opts.limit ?? DEFAULT_LIMIT
828
+ });
829
+ return rows.map((row) => ({
830
+ name: row.toolName,
831
+ invocations: row.invocations,
832
+ failures: row.failures,
833
+ durationMsTotal: row.durationMsTotal,
834
+ lastInvokedAt: row.lastInvokedAt,
835
+ daysSinceLastUse: row.lastInvokedAt === null ? null : daysBetween(now, row.lastInvokedAt),
836
+ source: "chronicle"
837
+ }));
838
+ }
839
+ function inProcessCandidates(usage, opts, now) {
840
+ const windowStart = now - opts.idleDays * 864e5;
841
+ const out = [];
842
+ for (const [name, rec] of usage) {
843
+ if (rec.lastInvokedAt >= windowStart) continue;
844
+ out.push({
845
+ name,
846
+ invocations: rec.invocations,
847
+ failures: rec.failures,
848
+ durationMsTotal: rec.durationMsTotal,
849
+ lastInvokedAt: rec.lastInvokedAt,
850
+ daysSinceLastUse: daysBetween(now, rec.lastInvokedAt),
851
+ source: "in-process"
852
+ });
853
+ }
854
+ out.sort((a, b) => a.invocations - b.invocations || a.name.localeCompare(b.name));
855
+ return out.slice(0, opts.limit ?? DEFAULT_LIMIT);
856
+ }
857
+ function createToolUsageSource(deps) {
858
+ const now = deps.now ?? (() => Date.now());
859
+ if (deps.chronicle) {
860
+ const store = deps.chronicle;
861
+ return {
862
+ kind: "chronicle",
863
+ async candidates(opts) {
864
+ await store.refresh();
865
+ return chronicleCandidates(store, opts, now());
866
+ },
867
+ async refresh() {
868
+ await store.refresh();
869
+ }
870
+ };
871
+ }
872
+ if (deps.bridge) {
873
+ const usage = deps.bridge;
874
+ return {
875
+ kind: "in-process",
876
+ async candidates(opts) {
877
+ return inProcessCandidates(usage, opts, now());
878
+ }
879
+ };
880
+ }
881
+ return {
882
+ kind: "in-process",
883
+ async candidates() {
884
+ return [];
885
+ }
886
+ };
887
+ }
888
+ function filterUnderused(candidates, opts, now = Date.now()) {
889
+ const windowStart = now - opts.idleDays * 864e5;
890
+ return candidates.filter((c) => {
891
+ if (c.invocations > opts.minInvocations) return false;
892
+ if (c.lastInvokedAt === null) return true;
893
+ return c.lastInvokedAt < windowStart;
894
+ });
895
+ }
896
+
897
+ // src/observability/tracer.ts
898
+ var NoopTracer = class {
899
+ startSpan() {
900
+ return NOOP_SPAN;
901
+ }
902
+ };
903
+ var NOOP_SPAN = {
904
+ setAttribute() {
905
+ },
906
+ recordError() {
907
+ },
908
+ end() {
909
+ }
910
+ };
1067
911
  export {
1068
912
  DefaultHealthRegistry,
1069
913
  InMemoryMetricsSink,
@@ -1073,9 +917,11 @@ export {
1073
917
  PROMETHEUS_CONTENT_TYPE,
1074
918
  buildOtlpMetricsRequest,
1075
919
  buildOtlpTracesRequest,
920
+ createToolUsageSource,
1076
921
  emitProcessCompleted,
1077
922
  emitProcessOutput,
1078
923
  emitProcessStarted,
924
+ filterUnderused,
1079
925
  redactCommand,
1080
926
  redactCommandArgs,
1081
927
  renderPrometheus,