@wrongstack/core 0.313.1 → 0.316.2

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 (290) hide show
  1. package/dist/chronicle/index.js +31 -31
  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 +39 -31
  7. package/dist/chronicle/sink.d.ts +13 -0
  8. package/dist/chronicle/sqlite-journal.d.ts +20 -2
  9. package/dist/{chunk-WOEZV3Q4.js → chunk-25LSLC3G.js} +2 -1
  10. package/dist/{chunk-AI4LLQ2U.js → chunk-27QIURLN.js} +229 -12
  11. package/dist/{chunk-I45KAW2N.js → chunk-3AGWVSS7.js} +38 -450
  12. package/dist/chunk-3BDV7RL3.js +211 -0
  13. package/dist/{chunk-NFAYHPRH.js → chunk-3ECENVHL.js} +10 -8
  14. package/dist/{chunk-FW5LDDMJ.js → chunk-45WI72ZB.js} +1576 -1156
  15. package/dist/{chunk-AT4V4XGI.js → chunk-4BMVPOHO.js} +539 -313
  16. package/dist/{chunk-YBUJCZ57.js → chunk-4I7HGVVA.js} +17 -5
  17. package/dist/{chunk-JVGPR3TS.js → chunk-4LCZVJVF.js} +7 -55
  18. package/dist/{chunk-4ZHTKJHM.js → chunk-4SJNS7OB.js} +36 -2
  19. package/dist/{chunk-AD7ZFZCS.js → chunk-5BMIE7QG.js} +3 -3
  20. package/dist/{chunk-IQ35HMQL.js → chunk-5YPSXQXM.js} +71 -24
  21. package/dist/chunk-6FHWXLPT.js +10 -0
  22. package/dist/{chunk-FIV4IJL7.js → chunk-6H5GEOET.js} +163 -20
  23. package/dist/{chunk-VB3H5WWB.js → chunk-6NWEVFEL.js} +2 -2
  24. package/dist/{chunk-E6I5RXVK.js → chunk-AYW5YINX.js} +2 -2
  25. package/dist/{chunk-DEIQMBD2.js → chunk-B7F4WVP4.js} +2 -2
  26. package/dist/{chunk-XQ2XGIBF.js → chunk-BPBKKBUK.js} +6 -7
  27. package/dist/{chunk-K4YUNKU7.js → chunk-BYD345OX.js} +2 -2
  28. package/dist/{chunk-LZDDLNCZ.js → chunk-C27KDLOX.js} +4 -4
  29. package/dist/{chunk-6AKOTGLM.js → chunk-CU546ESW.js} +7 -7
  30. package/dist/{chunk-ZZ4V3WIZ.js → chunk-DLHHXUAM.js} +10 -1
  31. package/dist/{chunk-BAKVJLEV.js → chunk-DOTHQSJ2.js} +28 -16
  32. package/dist/{chunk-KBZGE6LN.js → chunk-DYLPMV4Q.js} +4 -3
  33. package/dist/{chunk-52RTVH4M.js → chunk-F3JWNST7.js} +2 -2
  34. package/dist/{chunk-WEBMSQP2.js → chunk-FC2ZGRF3.js} +21 -4
  35. package/dist/{chunk-FHAMQS7Q.js → chunk-FCBTO76Z.js} +76 -146
  36. package/dist/{chunk-4LJJYBAU.js → chunk-FHZVFYSL.js} +9 -1
  37. package/dist/{chunk-W4A6QA3D.js → chunk-FWNLEZYG.js} +4 -3
  38. package/dist/{chunk-OJZEX73F.js → chunk-GDQ7CZRQ.js} +5 -1
  39. package/dist/{chunk-QVOVMXJR.js → chunk-GJJGJB2R.js} +32 -9
  40. package/dist/{chunk-4XV6IR37.js → chunk-HI3JGZR2.js} +7 -6
  41. package/dist/{chunk-FSWCWSTS.js → chunk-HMLIGAU7.js} +26 -26
  42. package/dist/{chunk-PFXFQQRT.js → chunk-HXKZ7OBQ.js} +7 -43
  43. package/dist/{chunk-2FQL4PDC.js → chunk-I7DALG7J.js} +5 -5
  44. package/dist/{chunk-GM54GJBU.js → chunk-IRUTEHCE.js} +3 -3
  45. package/dist/{chunk-TYW2GI5I.js → chunk-J2WRWR37.js} +40 -13
  46. package/dist/{chunk-ZEHUCJHY.js → chunk-JFPJ6LLQ.js} +2 -2
  47. package/dist/chunk-JMULFFLN.js +9 -0
  48. package/dist/{chunk-RD6DEE36.js → chunk-JO4MGB6Z.js} +94 -8
  49. package/dist/{chunk-5DSMEVA4.js → chunk-K6BXGITA.js} +7 -7
  50. package/dist/{chunk-O74MJHXU.js → chunk-KZMSZV32.js} +2 -2
  51. package/dist/{chunk-GFJOMSHG.js → chunk-LA7CLDRW.js} +6 -2
  52. package/dist/{chunk-M2WF6AXG.js → chunk-LSMMOS2Z.js} +7 -3
  53. package/dist/{chunk-ASX6AW36.js → chunk-LXWCMQNJ.js} +3 -3
  54. package/dist/{chunk-WZO23AWO.js → chunk-M27LIEKV.js} +72 -22
  55. package/dist/{chunk-JTHKOYWU.js → chunk-MJPHJRAL.js} +15 -15
  56. package/dist/{chunk-G5AAKEEZ.js → chunk-MTL4O5YR.js} +5 -5
  57. package/dist/chunk-NHJ75HXY.js +1 -0
  58. package/dist/{chunk-ZYU2YOJ6.js → chunk-NIOAGFPB.js} +29 -15
  59. package/dist/{chunk-Y2GE5TSI.js → chunk-NS52ISWS.js} +2 -2
  60. package/dist/{chunk-UIWONWVG.js → chunk-NSOFPKGT.js} +541 -148
  61. package/dist/{chunk-RCQYT2I4.js → chunk-NVIZG63R.js} +2 -2
  62. package/dist/{chunk-EPC3GZD4.js → chunk-OOYZPOXM.js} +2 -2
  63. package/dist/{chunk-7ZHZE5EO.js → chunk-OSGLYW5J.js} +2 -2
  64. package/dist/{chunk-CE4ZOCOI.js → chunk-PFCMSSUF.js} +9 -9
  65. package/dist/{chunk-GFXZTHNI.js → chunk-PGRVXIMY.js} +5 -5
  66. package/dist/{chunk-JJPMWPB6.js → chunk-PISBFBJN.js} +6 -6
  67. package/dist/chunk-POOOIWE5.js +9 -0
  68. package/dist/{chunk-5RP3QTRL.js → chunk-PQNS3U7Z.js} +20 -3
  69. package/dist/{chunk-3E244UDA.js → chunk-QSTSJOCK.js} +52 -6
  70. package/dist/{chunk-YUPMXOGP.js → chunk-RFQMOKWK.js} +3 -3
  71. package/dist/chunk-RTOTNK3P.js +484 -0
  72. package/dist/{chunk-SN2DQRL6.js → chunk-RXKSDUAU.js} +7 -7
  73. package/dist/{chunk-T3KGZBU5.js → chunk-S45JPKQN.js} +189 -43
  74. package/dist/{chunk-YNCVSFGF.js → chunk-SAFHACFA.js} +4 -1
  75. package/dist/{chunk-KI4L2DUL.js → chunk-TCCHINEF.js} +114 -19
  76. package/dist/{chunk-AWNQ6ED6.js → chunk-THEGTRAG.js} +7 -2
  77. package/dist/{chunk-2KKODIQU.js → chunk-TR4LQE7L.js} +15 -15
  78. package/dist/{chunk-GPQ3NJPR.js → chunk-TWJMGXCI.js} +3 -3
  79. package/dist/{chunk-H72H4XDU.js → chunk-U45AXT5D.js} +9 -9
  80. package/dist/{chunk-62V2AFVG.js → chunk-U4FWBSO3.js} +160 -73
  81. package/dist/{chunk-A455ATZB.js → chunk-UBGDX6FI.js} +8 -1
  82. package/dist/chunk-UJILKYES.js +75 -0
  83. package/dist/{chunk-KSJ5UTP7.js → chunk-ULMAQV3T.js} +84 -4
  84. package/dist/{chunk-CB2HFKTP.js → chunk-VVBP6KGT.js} +1 -1
  85. package/dist/{chunk-RPOA7IAE.js → chunk-W5VITDM2.js} +43 -25
  86. package/dist/{chunk-QRTG4XTI.js → chunk-WAHQVNWT.js} +7 -1
  87. package/dist/{chunk-SQP64RCS.js → chunk-WGGYRBXH.js} +3019 -2976
  88. package/dist/{chunk-K3G6JBUC.js → chunk-WLYKU45P.js} +679 -141
  89. package/dist/chunk-WP7BVANJ.js +53 -0
  90. package/dist/{chunk-GAZEWQEZ.js → chunk-XF4VUMKW.js} +2 -2
  91. package/dist/{chunk-PAAWLLZX.js → chunk-Y5YXDM7D.js} +2 -2
  92. package/dist/{chunk-65RDIOR2.js → chunk-YOPAUVU4.js} +162 -44
  93. package/dist/{chunk-FFSX2QDQ.js → chunk-YPT4A46F.js} +3 -3
  94. package/dist/chunk-YWXT3JNM.js +425 -0
  95. package/dist/{chunk-EFEHDQGY.js → chunk-ZHZ2Y2DH.js} +64 -10
  96. package/dist/{chunk-CQQL4ZOQ.js → chunk-ZT7JNIGX.js} +4 -3
  97. package/dist/coordination/agent-monitor.d.ts +9 -1
  98. package/dist/coordination/agent-subagent-runner.d.ts +1 -1
  99. package/dist/coordination/agents/agent-prompts.d.ts +8 -0
  100. package/dist/coordination/agents/index.d.ts +33 -1
  101. package/dist/coordination/agents/index.js +52 -22
  102. package/dist/coordination/agents/phase3-wave1-platform.d.ts +1 -3
  103. package/dist/coordination/agents/phase3-wave2-meta.d.ts +1 -3
  104. package/dist/coordination/agents/phase8-wave3-products.d.ts +1 -3
  105. package/dist/coordination/agents/phase9-wave4-platform-meta.d.ts +1 -3
  106. package/dist/coordination/agents/project-agent-identity.d.ts +11 -11
  107. package/dist/coordination/agents/types.d.ts +13 -2
  108. package/dist/coordination/brain-monitor.d.ts +30 -7
  109. package/dist/coordination/collab-debug.d.ts +1 -1
  110. package/dist/coordination/collab-pause.d.ts +1 -1
  111. package/dist/coordination/delegate-tool.d.ts +7 -5
  112. package/dist/coordination/director-kanban-queue-helpers.d.ts +1 -0
  113. package/dist/coordination/director-options.d.ts +7 -0
  114. package/dist/coordination/director-spawn-model.d.ts +17 -0
  115. package/dist/coordination/director-tools.d.ts +2 -2
  116. package/dist/coordination/director.d.ts +12 -0
  117. package/dist/coordination/dispatcher.d.ts +8 -0
  118. package/dist/coordination/fleet-bus.d.ts +35 -4
  119. package/dist/coordination/fleet-supervisor.d.ts +10 -2
  120. package/dist/coordination/fleet.d.ts +16 -7
  121. package/dist/coordination/index.d.ts +4 -0
  122. package/dist/coordination/index.js +86 -49
  123. package/dist/coordination/kanban-dispatch-port.d.ts +2 -2
  124. package/dist/coordination/mailbox-http-router.d.ts +4 -3
  125. package/dist/coordination/mailbox-http-validation.d.ts +1 -1
  126. package/dist/coordination/mailbox-project-server.js +15 -15
  127. package/dist/coordination/mailbox-types.d.ts +24 -6
  128. package/dist/coordination/model-tier-leader.d.ts +122 -0
  129. package/dist/coordination/model-tier.d.ts +122 -0
  130. package/dist/coordination/multi-agent-coordinator.d.ts +24 -0
  131. package/dist/coordination/origin-session.d.ts +16 -0
  132. package/dist/coordination/provider-status-tracker.d.ts +37 -5
  133. package/dist/coordination/session-note-hub.d.ts +44 -0
  134. package/dist/coordination/session-note-tool.d.ts +10 -0
  135. package/dist/core/agent.d.ts +1 -1
  136. package/dist/core/context.d.ts +22 -3
  137. package/dist/core/index.d.ts +2 -0
  138. package/dist/core/index.js +130 -38
  139. package/dist/core/request-conversation-binding.d.ts +37 -0
  140. package/dist/core/session-notes.d.ts +25 -0
  141. package/dist/core/system-prompt-builder.d.ts +11 -2
  142. package/dist/core/system-prompt-variants.d.ts +78 -0
  143. package/dist/design/index.js +3 -3
  144. package/dist/execution/auto-compaction-middleware.d.ts +11 -0
  145. package/dist/execution/autonomy-brain.d.ts +2 -1
  146. package/dist/execution/autonomy-prompt-contributor.d.ts +7 -2
  147. package/dist/execution/compaction-core.d.ts +4 -4
  148. package/dist/execution/eternal-autonomy.d.ts +1 -1
  149. package/dist/execution/index.js +138 -129
  150. package/dist/execution/model-runtime.d.ts +0 -7
  151. package/dist/execution/parallel-eternal-engine.d.ts +7 -7
  152. package/dist/extension/index.js +3 -3
  153. package/dist/goal/index.js +6 -6
  154. package/dist/hooks/index.js +3 -3
  155. package/dist/hq/index.js +14 -12
  156. package/dist/hq/persistence/event-log.d.ts +0 -1
  157. package/dist/hq/persistence.d.ts +4 -4
  158. package/dist/hq/publisher.d.ts +2 -0
  159. package/dist/index.d.ts +14 -11
  160. package/dist/index.js +728 -635
  161. package/dist/infrastructure/index.d.ts +4 -4
  162. package/dist/infrastructure/index.js +281 -260
  163. package/dist/infrastructure/token-counter.d.ts +9 -0
  164. package/dist/kernel/events/agent-events.d.ts +77 -3
  165. package/dist/kernel/events/brain-events.d.ts +12 -0
  166. package/dist/kernel/events/provider-events.d.ts +7 -1
  167. package/dist/kernel/events/session-events.d.ts +14 -0
  168. package/dist/kernel/events/tool-events.d.ts +13 -0
  169. package/dist/kernel/index.js +4 -4
  170. package/dist/mailbox-attach.d.ts +1 -1
  171. package/dist/models/index.js +10 -9
  172. package/dist/observability/event-bridge.d.ts +25 -5
  173. package/dist/observability/index.d.ts +9 -8
  174. package/dist/observability/index.js +459 -351
  175. package/dist/observability/tool-usage-source.d.ts +65 -0
  176. package/dist/plugin/index.d.ts +5 -0
  177. package/dist/plugin/index.js +66 -50
  178. package/dist/plugins/auto-review-plugin.d.ts +3 -2
  179. package/dist/plugins/review-finding-commands.d.ts +0 -8
  180. package/dist/{review-finding-store-DKAV3AS6.js → plugins/review-finding-store.js} +7 -6
  181. package/dist/plugins/review-finding-types.js +19 -0
  182. package/dist/plugins/review-report-integration.js +21 -0
  183. package/dist/{review-report-store-OWY3EC3N.js → plugins/review-report-store.js} +7 -6
  184. package/dist/plugins/review-report-types.js +13 -0
  185. package/dist/prompts/index.js +5 -5
  186. package/dist/registry/index.js +3 -3
  187. package/dist/registry/tool-registry.d.ts +59 -9
  188. package/dist/{registry-R23UBE7H.js → registry-7N4O3E7W.js} +5 -5
  189. package/dist/security/capabilities.d.ts +6 -0
  190. package/dist/security/file-permissions.d.ts +1 -1
  191. package/dist/security/index.js +47 -33
  192. package/dist/security/permission-policy.d.ts +12 -1
  193. package/dist/security/secret-scrubber.d.ts +15 -0
  194. package/dist/session-catalog/index.d.ts +1 -0
  195. package/dist/session-catalog/index.js +19 -14
  196. package/dist/session-catalog/project-server.js +30 -13
  197. package/dist/session-catalog/protocol.d.ts +32 -1
  198. package/dist/session-catalog/registry.d.ts +17 -1
  199. package/dist/session-catalog/session-agents.d.ts +56 -0
  200. package/dist/session-catalog/store-rebuild.d.ts +14 -0
  201. package/dist/session-catalog/store-schema.d.ts +26 -0
  202. package/dist/session-catalog/store.d.ts +27 -1
  203. package/dist/session-note-attach.d.ts +9 -0
  204. package/dist/skills/index.js +5 -5
  205. package/dist/statusline/index.d.ts +43 -0
  206. package/dist/statusline/index.js +102 -0
  207. package/dist/storage/board-store-port.d.ts +2 -2
  208. package/dist/storage/config-loader/in-project-policy.d.ts +29 -0
  209. package/dist/storage/config-loader.d.ts +3 -2
  210. package/dist/storage/file-session-writer.d.ts +4 -2
  211. package/dist/storage/goal-coordination.d.ts +6 -1
  212. package/dist/storage/goal-kanban.d.ts +4 -1
  213. package/dist/storage/index.d.ts +12 -4
  214. package/dist/storage/index.js +76 -61
  215. package/dist/storage/input-history-store.d.ts +2 -4
  216. package/dist/storage/memory-curator.d.ts +133 -0
  217. package/dist/storage/session-agent-attribution.d.ts +39 -0
  218. package/dist/storage/session-doctor.d.ts +109 -0
  219. package/dist/storage/session-event-bridge.d.ts +5 -7
  220. package/dist/storage/session-outcome.d.ts +42 -0
  221. package/dist/storage/session-recovery.d.ts +55 -0
  222. package/dist/storage/session-store/load-session-data.d.ts +11 -0
  223. package/dist/storage/session-store/prune-helpers.d.ts +1 -1
  224. package/dist/storage/session-store/resume-session.d.ts +0 -2
  225. package/dist/storage/session-store/types.d.ts +8 -0
  226. package/dist/storage/session-store.d.ts +3 -3
  227. package/dist/storage/session-summary-tracker.d.ts +8 -1
  228. package/dist/storage/session-write-buffer.d.ts +18 -0
  229. package/dist/storage/session-writer-truncate.d.ts +2 -0
  230. package/dist/tasking/index.js +2 -2
  231. package/dist/tools/fallback-manage-tools.d.ts +6 -4
  232. package/dist/tools/index.js +21 -21
  233. package/dist/tools/model-tier-set-tool.d.ts +66 -0
  234. package/dist/types/agent-bridge.d.ts +2 -1
  235. package/dist/types/config/mcp-features.d.ts +7 -0
  236. package/dist/types/config/model-tiers.d.ts +148 -0
  237. package/dist/types/config/root.d.ts +9 -0
  238. package/dist/types/config/tools.d.ts +73 -0
  239. package/dist/types/config.d.ts +1 -0
  240. package/dist/types/context.d.ts +2 -0
  241. package/dist/types/default-config.d.ts +6 -0
  242. package/dist/types/errors.d.ts +2 -1
  243. package/dist/types/index.d.ts +11 -9
  244. package/dist/types/index.js +25 -15
  245. package/dist/types/multi-agent.d.ts +43 -0
  246. package/dist/types/plugin.d.ts +1 -1
  247. package/dist/types/provider.d.ts +15 -0
  248. package/dist/types/runtime-capability-manifest.d.ts +1 -1
  249. package/dist/types/secret-scrubber.d.ts +15 -0
  250. package/dist/types/session-markers.d.ts +37 -10
  251. package/dist/types/session-markers.js +1 -1
  252. package/dist/types/session-timeline.d.ts +197 -0
  253. package/dist/types/session-timeline.js +12 -0
  254. package/dist/types/session.d.ts +172 -11
  255. package/dist/types/system-prompt.d.ts +27 -0
  256. package/dist/utils/config-backup.d.ts +9 -1
  257. package/dist/utils/heap-watchdog.js +2 -2
  258. package/dist/utils/index.js +50 -48
  259. package/dist/utils/message-invariants.d.ts +4 -1
  260. package/dist/utils/regex-guard.d.ts +1 -1
  261. package/dist/utils/socket-path.d.ts +1 -1
  262. package/dist/utils/token-estimate.d.ts +6 -2
  263. package/dist/utils/wstack-paths.d.ts +2 -0
  264. package/dist/wiring/proxy-rewrite.d.ts +28 -0
  265. package/dist/wiring/proxy-rewrite.js +7 -1
  266. package/dist/worktree/index.js +2 -2
  267. package/instructions/agents/explore-companion.md +45 -18
  268. package/instructions/agents/memory-curator.md +34 -22
  269. package/instructions/coordination/director-preamble.md +12 -7
  270. package/instructions/coordination/subagent-baseline.md +26 -5
  271. package/instructions/leader-after-task.md +4 -1
  272. package/instructions/llm/chimera-review.md +30 -9
  273. package/instructions/llm/memory-curator.md +31 -0
  274. package/instructions/sections/tool/delegation-compact.md +2 -0
  275. package/instructions/sections/tool/delegation-full.md +5 -1
  276. package/instructions/sections/tool/mailbox-compact.md +5 -1
  277. package/instructions/sections/tool/mailbox-full.md +3 -0
  278. package/instructions/sections/tool/session-note-compact.md +3 -0
  279. package/instructions/sections/tool/session-note-full.md +15 -0
  280. package/instructions/system-lite.md +9 -4
  281. package/instructions/system-pro.md +66 -143
  282. package/instructions/system.md +73 -175
  283. package/package.json +33 -5
  284. package/dist/chunk-2BUUIMQB.js +0 -225
  285. package/dist/chunk-FAYRX2AD.js +0 -13
  286. package/dist/chunk-H5E77KUJ.js +0 -15
  287. package/dist/chunk-M7BS2K6U.js +0 -1
  288. package/dist/coordination/agents.d.ts +0 -8
  289. package/dist/coordination/transport.d.ts +0 -2
  290. package/dist/storage/session-writer/session-writer-summary-tracker.d.ts +0 -28
@@ -10,10 +10,151 @@ import {
10
10
  runWithNetworkTelemetry,
11
11
  startNetworkTelemetryMonitor
12
12
  } from "../chunk-HA4ZYA6T.js";
13
+ import "../chunk-PFZO3C6H.js";
13
14
  import {
14
15
  toErrorMessage
15
16
  } from "../chunk-TBRL2RCC.js";
16
- import "../chunk-PFZO3C6H.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
+ };
17
158
 
18
159
  // src/observability/metrics.ts
19
160
  var RESERVOIR_SIZE = 1024;
@@ -187,357 +328,59 @@ var InMemoryMetricsSink = class {
187
328
  }
188
329
  };
189
330
  function parseLabelKey(key) {
190
- if (!key) return {};
191
- const labels = {};
192
- for (const pair of key.split(",")) {
193
- const eq = pair.indexOf("=");
194
- if (eq > 0) labels[pair.slice(0, eq)] = pair.slice(eq + 1);
195
- }
196
- return labels;
197
- }
198
- var NoopMetricsSink = class {
199
- counter() {
200
- }
201
- gauge() {
202
- }
203
- histogram() {
204
- }
205
- snapshot() {
206
- return { timestamp: Date.now(), series: [] };
207
- }
208
- droppedObservations() {
209
- return 0;
210
- }
211
- reset() {
212
- }
213
- };
214
-
215
- // src/observability/health.ts
216
- var SEVERITY = {
217
- healthy: 0,
218
- degraded: 1,
219
- unhealthy: 2
220
- };
221
- var DefaultHealthRegistry = class {
222
- checks = /* @__PURE__ */ new Map();
223
- timeoutMs;
224
- constructor(opts = {}) {
225
- this.timeoutMs = opts.timeoutMs ?? 5e3;
226
- }
227
- register(check) {
228
- this.checks.set(check.name, check);
229
- }
230
- unregister(name) {
231
- this.checks.delete(name);
232
- }
233
- async run() {
234
- const results = await Promise.all(
235
- Array.from(this.checks.values()).map(async (c) => {
236
- const result = await this.runOne(c);
237
- return { name: c.name, ...result };
238
- })
239
- );
240
- let status = "healthy";
241
- for (const r of results) {
242
- if (SEVERITY[r.status] > SEVERITY[status]) status = r.status;
243
- }
244
- return { status, timestamp: Date.now(), checks: results };
245
- }
246
- async runOne(check) {
247
- let timer = null;
248
- const timeout = new Promise((resolve) => {
249
- timer = setTimeout(
250
- () => resolve({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
251
- this.timeoutMs
252
- );
253
- });
254
- try {
255
- return await Promise.race([check.check(), timeout]);
256
- } catch (err) {
257
- return { status: "unhealthy", detail: toErrorMessage(err) };
258
- } finally {
259
- if (timer) clearTimeout(timer);
260
- }
261
- }
262
- };
263
-
264
- // src/observability/tracer.ts
265
- var NoopTracer = class {
266
- startSpan() {
267
- return NOOP_SPAN;
268
- }
269
- };
270
- var NOOP_SPAN = {
271
- setAttribute() {
272
- },
273
- recordError() {
274
- },
275
- end() {
276
- }
277
- };
278
-
279
- // src/observability/otel-tracer.ts
280
- var OTEL_STATUS_ERROR = 2;
281
- var OTelTracer = class {
282
- constructor(upstream) {
283
- this.upstream = upstream;
284
- }
285
- upstream;
286
- startSpan(name, attrs) {
287
- const otelSpan = this.upstream.startSpan(name, attrs ? { attributes: attrs } : void 0);
288
- return new OTelSpan(otelSpan);
289
- }
290
- };
291
- var OTelSpan = class {
292
- constructor(span) {
293
- this.span = span;
294
- }
295
- span;
296
- setAttribute(key, value) {
297
- this.span.setAttribute(key, value);
298
- }
299
- recordError(err) {
300
- this.span.recordException(err);
301
- this.span.setStatus?.({ code: OTEL_STATUS_ERROR, message: err.message });
302
- }
303
- end() {
304
- this.span.end();
305
- }
306
- };
307
-
308
- // src/observability/event-bridge.ts
309
- function wireMetricsToEvents(events, sink) {
310
- const unsubs = [];
311
- unsubs.push(
312
- events.on("session.started", () => sink.counter("agent.sessions.started")),
313
- events.on("session.ended", (e) => {
314
- sink.counter("agent.sessions.ended");
315
- sink.histogram("agent.session.tokens.input", e.usage.input);
316
- sink.histogram("agent.session.tokens.output", e.usage.output);
317
- }),
318
- events.on("session.damaged", () => sink.counter("agent.sessions.damaged")),
319
- events.on("iteration.completed", () => sink.counter("agent.iterations.total")),
320
- events.on("iteration.limit_reached", () => sink.counter("agent.iteration_limit.hit")),
321
- events.on("provider.response", (e) => {
322
- sink.counter("provider.responses.total", 1, { stop_reason: e.stopReason });
323
- sink.counter("provider.tokens.input", e.usage.input);
324
- sink.counter("provider.tokens.output", e.usage.output);
325
- if (e.usage.cacheRead) sink.counter("provider.tokens.cache_read", e.usage.cacheRead);
326
- if (e.usage.cacheWrite) sink.counter("provider.tokens.cache_write", e.usage.cacheWrite);
327
- }),
328
- events.on(
329
- "provider.retry",
330
- (e) => sink.counter("provider.retries.total", 1, {
331
- provider: e.providerId,
332
- status: String(e.status)
333
- })
334
- ),
335
- events.on(
336
- "provider.error",
337
- (e) => sink.counter("provider.errors.total", 1, {
338
- provider: e.providerId,
339
- status: String(e.status),
340
- retryable: String(e.retryable)
341
- })
342
- ),
343
- events.on(
344
- "tool.started",
345
- (e) => sink.counter("tool.starts.total", 1, { tool: metricToolName(e.name) })
346
- ),
347
- events.on("tool.executed", (e) => {
348
- const tool = metricToolName(e.name);
349
- sink.counter("tool.executions.total", 1, { tool, ok: String(e.ok) });
350
- sink.histogram("tool.duration_ms", e.durationMs, { tool });
351
- }),
352
- events.on("token.threshold", (e) => sink.gauge("agent.tokens.used", e.used)),
353
- events.on("compaction.fired", (e) => {
354
- sink.counter("compaction.fired.total");
355
- sink.histogram("compaction.reduction_tokens", e.report.before - e.report.after);
356
- }),
357
- // MCP server names are user-controlled and may contain tenant/project
358
- // identifiers. Keep them out of metric labels to bound cardinality and
359
- // prevent operational metadata leaks.
360
- events.on("mcp.server.connected", () => sink.counter("mcp.connects.total")),
361
- events.on("mcp.server.reconnected", () => sink.counter("mcp.reconnects.total")),
362
- events.on("mcp.server.disconnected", () => sink.counter("mcp.disconnects.total")),
363
- events.on("error", (e) => sink.counter("agent.errors.total", 1, { phase: e.phase }))
364
- );
365
- return () => {
366
- for (const u of unsubs) u();
367
- };
368
- }
369
- function metricToolName(name) {
370
- return name.startsWith("mcp__") ? "mcp_proxy" : name;
371
- }
372
-
373
- // src/observability/prometheus.ts
374
- import { timingSafeEqual } from "node:crypto";
375
- var NUMBER_FORMAT_INFINITY = "NaN";
376
- function escapeLabelValue(v) {
377
- return v.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/"/g, '\\"');
378
- }
379
- function formatLabels(labels) {
380
- const keys = Object.keys(labels);
381
- if (keys.length === 0) return "";
382
- const parts = keys.map((k) => `${k}="${escapeLabelValue(labels[k] ?? "")}"`);
383
- return `{${parts.join(",")}}`;
384
- }
385
- function formatNumber(n) {
386
- if (!Number.isFinite(n)) return NUMBER_FORMAT_INFINITY;
387
- return Number.isInteger(n) ? n.toString() : n.toString();
388
- }
389
- function joinLabels(base, extra) {
390
- return { ...base, ...extra };
391
- }
392
- function renderPrometheus(snapshot) {
393
- const groups = /* @__PURE__ */ new Map();
394
- for (const s of snapshot.series) {
395
- let g = groups.get(s.name);
396
- if (!g) {
397
- g = { type: s.type, rows: [] };
398
- groups.set(s.name, g);
399
- }
400
- g.rows.push({ labels: s.labels, values: s.values });
401
- }
402
- const lines = [];
403
- for (const [name, g] of groups) {
404
- const promType = g.type === "histogram" ? "summary" : g.type;
405
- lines.push(`# HELP ${name} ${name}`);
406
- lines.push(`# TYPE ${name} ${promType}`);
407
- if (g.type === "counter" || g.type === "gauge") {
408
- for (const row of g.rows) {
409
- lines.push(`${name}${formatLabels(row.labels)} ${formatNumber(row.values.value ?? 0)}`);
410
- }
411
- } else {
412
- for (const row of g.rows) {
413
- const { count = 0, sum = 0, p50 = 0, p95 = 0, p99 = 0 } = row.values;
414
- lines.push(
415
- `${name}${formatLabels(joinLabels(row.labels, { quantile: "0.5" }))} ${formatNumber(p50)}`
416
- );
417
- lines.push(
418
- `${name}${formatLabels(joinLabels(row.labels, { quantile: "0.95" }))} ${formatNumber(p95)}`
419
- );
420
- lines.push(
421
- `${name}${formatLabels(joinLabels(row.labels, { quantile: "0.99" }))} ${formatNumber(p99)}`
422
- );
423
- lines.push(`${name}_sum${formatLabels(row.labels)} ${formatNumber(sum)}`);
424
- lines.push(`${name}_count${formatLabels(row.labels)} ${formatNumber(count)}`);
425
- }
426
- }
427
- }
428
- return lines.join("\n") + "\n";
429
- }
430
- var PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
431
- function isLoopbackBindHost(host) {
432
- const h = host.trim().toLowerCase().replace(/^\[|\]$/g, "");
433
- return h === "127.0.0.1" || h === "localhost" || h === "::1" || h.startsWith("127.");
434
- }
435
- function bearerMatches(header, token) {
436
- if (!header) return false;
437
- const prefix = "bearer ";
438
- if (!header.toLowerCase().startsWith(prefix)) return false;
439
- const supplied = Buffer.from(header.slice(prefix.length).trim(), "utf8");
440
- const expected = Buffer.from(token, "utf8");
441
- if (supplied.length !== expected.length) return false;
442
- return timingSafeEqual(supplied, expected);
443
- }
444
- async function startMetricsServer(opts) {
445
- const tls = opts.tls;
446
- const useHttps = !!(tls?.cert && tls?.key);
447
- const host = opts.host ?? "127.0.0.1";
448
- const path = opts.path ?? "/metrics";
449
- const healthPath = opts.healthPath ?? "/healthz";
450
- const healthRegistry = opts.healthRegistry;
451
- const token = opts.token;
452
- if (!isLoopbackBindHost(host) && !token) {
453
- throw new Error(
454
- `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.`
455
- );
456
- }
457
- const listener = (req, res) => {
458
- if (!req.url || req.method !== "GET") {
459
- res.statusCode = req.url ? 405 : 400;
460
- res.end();
461
- return;
462
- }
463
- if (token && !bearerMatches(req.headers.authorization, token)) {
464
- res.statusCode = 401;
465
- res.setHeader("www-authenticate", "Bearer");
466
- res.setHeader("content-type", "text/plain; charset=utf-8");
467
- res.end("Unauthorized");
468
- return;
469
- }
470
- const url = req.url.split("?")[0];
471
- if (url === path) {
472
- let body;
473
- try {
474
- body = renderPrometheus(opts.sink.snapshot());
475
- } catch (err) {
476
- res.statusCode = 500;
477
- res.setHeader("content-type", "text/plain; charset=utf-8");
478
- res.end(`metrics render failed: ${toErrorMessage(err)}`);
479
- return;
480
- }
481
- res.statusCode = 200;
482
- res.setHeader("content-type", PROMETHEUS_CONTENT_TYPE);
483
- res.end(body);
484
- return;
485
- }
486
- if (healthRegistry && url === healthPath) {
487
- healthRegistry.run().then(
488
- (agg) => {
489
- res.statusCode = agg.status === "unhealthy" ? 503 : 200;
490
- res.setHeader("content-type", "application/json; charset=utf-8");
491
- res.end(JSON.stringify(agg, null, 2));
492
- },
493
- (err) => {
494
- res.statusCode = 500;
495
- res.setHeader("content-type", "text/plain; charset=utf-8");
496
- res.end(`health run failed: ${toErrorMessage(err)}`);
497
- }
498
- );
499
- return;
500
- }
501
- res.statusCode = 404;
502
- res.setHeader("content-type", "text/plain; charset=utf-8");
503
- res.end("Not Found");
504
- };
505
- let server;
506
- if (useHttps && tls) {
507
- const { createServer } = await import("node:https");
508
- const { readFileSync } = await import("node:fs");
509
- server = createServer(
510
- { cert: readFileSync(tls.cert), key: readFileSync(tls.key) },
511
- listener
512
- );
513
- } else {
514
- const { createServer } = await import("node:http");
515
- server = createServer(listener);
516
- }
517
- await new Promise((resolve, reject) => {
518
- const onError = (err) => {
519
- server.off("listening", onListening);
520
- reject(err);
521
- };
522
- const onListening = () => {
523
- server.off("error", onError);
524
- resolve();
525
- };
526
- server.once("error", onError);
527
- server.once("listening", onListening);
528
- server.listen(opts.port, host);
529
- });
530
- const addr = server.address();
531
- const boundPort = typeof addr === "object" && addr ? addr.port : opts.port;
532
- const protocol = useHttps ? "https" : "http";
533
- return {
534
- port: boundPort,
535
- url: `${protocol}://${host}:${boundPort}${path}`,
536
- close: () => new Promise((resolve, reject) => {
537
- server.close((err) => err ? reject(err) : resolve());
538
- })
539
- };
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;
540
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
+ };
541
384
 
542
385
  // src/observability/otlp-metrics.ts
543
386
  var DEFAULT_INTERVAL_MS = 3e4;
@@ -802,6 +645,269 @@ function startOtlpTraceExporter(opts) {
802
645
  buffered: () => [...buffer]
803
646
  };
804
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
+ };
805
911
  export {
806
912
  DefaultHealthRegistry,
807
913
  InMemoryMetricsSink,
@@ -811,9 +917,11 @@ export {
811
917
  PROMETHEUS_CONTENT_TYPE,
812
918
  buildOtlpMetricsRequest,
813
919
  buildOtlpTracesRequest,
920
+ createToolUsageSource,
814
921
  emitProcessCompleted,
815
922
  emitProcessOutput,
816
923
  emitProcessStarted,
924
+ filterUnderused,
817
925
  redactCommand,
818
926
  redactCommandArgs,
819
927
  renderPrometheus,