@newrelic/preflight 0.0.1-pre.1 → 1.0.0

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 (578) hide show
  1. package/LICENSE +183 -0
  2. package/README.md +498 -0
  3. package/dist/alerts/alert-log.d.ts +24 -0
  4. package/dist/alerts/alert-log.d.ts.map +1 -0
  5. package/dist/alerts/alert-log.js +159 -0
  6. package/dist/alerts/alert-log.js.map +1 -0
  7. package/dist/alerts/alert-snapshot-collector.d.ts +168 -0
  8. package/dist/alerts/alert-snapshot-collector.d.ts.map +1 -0
  9. package/dist/alerts/alert-snapshot-collector.js +243 -0
  10. package/dist/alerts/alert-snapshot-collector.js.map +1 -0
  11. package/dist/alerts/local-alert-engine.d.ts +86 -0
  12. package/dist/alerts/local-alert-engine.d.ts.map +1 -0
  13. package/dist/alerts/local-alert-engine.js +466 -0
  14. package/dist/alerts/local-alert-engine.js.map +1 -0
  15. package/dist/alerts/local-alert-rule.d.ts +439 -0
  16. package/dist/alerts/local-alert-rule.d.ts.map +1 -0
  17. package/dist/alerts/local-alert-rule.js +139 -0
  18. package/dist/alerts/local-alert-rule.js.map +1 -0
  19. package/dist/alerts/os-notifier.d.ts +39 -0
  20. package/dist/alerts/os-notifier.d.ts.map +1 -0
  21. package/dist/alerts/os-notifier.js +170 -0
  22. package/dist/alerts/os-notifier.js.map +1 -0
  23. package/dist/alerts/types.d.ts +35 -0
  24. package/dist/alerts/types.d.ts.map +1 -0
  25. package/dist/alerts/types.js +8 -0
  26. package/dist/alerts/types.js.map +1 -0
  27. package/dist/config.d.ts +169 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/config.js +860 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/dashboard/dashboard-server.d.ts +38 -0
  32. package/dist/dashboard/dashboard-server.d.ts.map +1 -0
  33. package/dist/dashboard/dashboard-server.js +207 -0
  34. package/dist/dashboard/dashboard-server.js.map +1 -0
  35. package/dist/dashboard/index.d.ts +3 -0
  36. package/dist/dashboard/index.d.ts.map +1 -0
  37. package/dist/dashboard/index.js +2 -0
  38. package/dist/dashboard/index.js.map +1 -0
  39. package/dist/dashboard/live-event-bus.d.ts +99 -0
  40. package/dist/dashboard/live-event-bus.d.ts.map +1 -0
  41. package/dist/dashboard/live-event-bus.js +56 -0
  42. package/dist/dashboard/live-event-bus.js.map +1 -0
  43. package/dist/dashboard/routes/api-handler.d.ts +122 -0
  44. package/dist/dashboard/routes/api-handler.d.ts.map +1 -0
  45. package/dist/dashboard/routes/api-handler.js +1414 -0
  46. package/dist/dashboard/routes/api-handler.js.map +1 -0
  47. package/dist/dashboard/routes/replay-analyzer.d.ts +15 -0
  48. package/dist/dashboard/routes/replay-analyzer.d.ts.map +1 -0
  49. package/dist/dashboard/routes/replay-analyzer.js +227 -0
  50. package/dist/dashboard/routes/replay-analyzer.js.map +1 -0
  51. package/dist/dashboard/routes/sse-handler.d.ts +4 -0
  52. package/dist/dashboard/routes/sse-handler.d.ts.map +1 -0
  53. package/dist/dashboard/routes/sse-handler.js +122 -0
  54. package/dist/dashboard/routes/sse-handler.js.map +1 -0
  55. package/dist/dashboard/routes/static-handler.d.ts +3 -0
  56. package/dist/dashboard/routes/static-handler.d.ts.map +1 -0
  57. package/dist/dashboard/routes/static-handler.js +103 -0
  58. package/dist/dashboard/routes/static-handler.js.map +1 -0
  59. package/dist/data/alerts/conditions/01-daily-cost-spike.json +16 -0
  60. package/dist/data/alerts/conditions/02-low-efficiency-score.json +16 -0
  61. package/dist/data/alerts/conditions/03-stuck-loop-rate.json +16 -0
  62. package/dist/data/alerts/conditions/04-anti-pattern-rate.json +16 -0
  63. package/dist/data/alerts/conditions/05-session-cost-budget.json +16 -0
  64. package/dist/data/alerts/conditions-personal/01-personal-daily-cost.json +16 -0
  65. package/dist/data/alerts/conditions-personal/02-personal-session-cost.json +16 -0
  66. package/dist/data/alerts/conditions-personal/03-personal-low-efficiency.json +16 -0
  67. package/dist/data/alerts/conditions-personal/04-personal-anti-pattern-rate.json +16 -0
  68. package/dist/data/alerts/conditions-personal/05-personal-stuck-loop.json +16 -0
  69. package/dist/data/alerts/policy.json +4 -0
  70. package/dist/data/dashboards/ai-coding-assistant-manager-view.json +103 -0
  71. package/dist/data/dashboards/ai-coding-assistant-overview.json +239 -0
  72. package/dist/data/dashboards/ai-coding-assistant-personal.json +442 -0
  73. package/dist/data/dashboards/ai-coding-assistant-platform-comparison.json +320 -0
  74. package/dist/data/dashboards/ai-coding-assistant-security.json +275 -0
  75. package/dist/data/dashboards/ai-coding-assistant-session-detail.json +296 -0
  76. package/dist/data/dashboards/ai-coding-assistant-team-view.json +345 -0
  77. package/dist/deploy/data-paths.d.ts +22 -0
  78. package/dist/deploy/data-paths.d.ts.map +1 -0
  79. package/dist/deploy/data-paths.js +69 -0
  80. package/dist/deploy/data-paths.js.map +1 -0
  81. package/dist/deploy/deploy-alerts.d.ts +58 -0
  82. package/dist/deploy/deploy-alerts.d.ts.map +1 -0
  83. package/dist/deploy/deploy-alerts.js +371 -0
  84. package/dist/deploy/deploy-alerts.js.map +1 -0
  85. package/dist/deploy/deploy-dashboards.d.ts +92 -0
  86. package/dist/deploy/deploy-dashboards.d.ts.map +1 -0
  87. package/dist/deploy/deploy-dashboards.js +282 -0
  88. package/dist/deploy/deploy-dashboards.js.map +1 -0
  89. package/dist/digest/digest-formatter.d.ts +3 -0
  90. package/dist/digest/digest-formatter.d.ts.map +1 -0
  91. package/dist/digest/digest-formatter.js +37 -0
  92. package/dist/digest/digest-formatter.js.map +1 -0
  93. package/dist/digest/digest-sender.d.ts +2 -0
  94. package/dist/digest/digest-sender.d.ts.map +1 -0
  95. package/dist/digest/digest-sender.js +29 -0
  96. package/dist/digest/digest-sender.js.map +1 -0
  97. package/dist/hooks/bash-classifier.d.ts +26 -0
  98. package/dist/hooks/bash-classifier.d.ts.map +1 -0
  99. package/dist/hooks/bash-classifier.js +409 -0
  100. package/dist/hooks/bash-classifier.js.map +1 -0
  101. package/dist/hooks/collector-script.d.ts +47 -0
  102. package/dist/hooks/collector-script.d.ts.map +1 -0
  103. package/dist/hooks/collector-script.js +662 -0
  104. package/dist/hooks/collector-script.js.map +1 -0
  105. package/dist/hooks/event-processor.d.ts +65 -0
  106. package/dist/hooks/event-processor.d.ts.map +1 -0
  107. package/dist/hooks/event-processor.js +342 -0
  108. package/dist/hooks/event-processor.js.map +1 -0
  109. package/dist/hooks/index.d.ts +7 -0
  110. package/dist/hooks/index.d.ts.map +1 -0
  111. package/dist/hooks/index.js +5 -0
  112. package/dist/hooks/index.js.map +1 -0
  113. package/dist/hooks/session-resolver.d.ts +66 -0
  114. package/dist/hooks/session-resolver.d.ts.map +1 -0
  115. package/dist/hooks/session-resolver.js +196 -0
  116. package/dist/hooks/session-resolver.js.map +1 -0
  117. package/dist/hooks/tool-parsers.d.ts +19 -0
  118. package/dist/hooks/tool-parsers.d.ts.map +1 -0
  119. package/dist/hooks/tool-parsers.js +260 -0
  120. package/dist/hooks/tool-parsers.js.map +1 -0
  121. package/dist/index.d.ts +107 -0
  122. package/dist/index.d.ts.map +1 -0
  123. package/dist/index.js +1505 -0
  124. package/dist/index.js.map +1 -0
  125. package/dist/install/cli.d.ts +11 -0
  126. package/dist/install/cli.d.ts.map +1 -0
  127. package/dist/install/cli.js +365 -0
  128. package/dist/install/cli.js.map +1 -0
  129. package/dist/install/index.d.ts +4 -0
  130. package/dist/install/index.d.ts.map +1 -0
  131. package/dist/install/index.js +3 -0
  132. package/dist/install/index.js.map +1 -0
  133. package/dist/install/install-helper.d.ts +35 -0
  134. package/dist/install/install-helper.d.ts.map +1 -0
  135. package/dist/install/install-helper.js +227 -0
  136. package/dist/install/install-helper.js.map +1 -0
  137. package/dist/install/key-validator.d.ts +19 -0
  138. package/dist/install/key-validator.d.ts.map +1 -0
  139. package/dist/install/key-validator.js +122 -0
  140. package/dist/install/key-validator.js.map +1 -0
  141. package/dist/install/migrate.d.ts +12 -0
  142. package/dist/install/migrate.d.ts.map +1 -0
  143. package/dist/install/migrate.js +115 -0
  144. package/dist/install/migrate.js.map +1 -0
  145. package/dist/install/schedule.d.ts +11 -0
  146. package/dist/install/schedule.d.ts.map +1 -0
  147. package/dist/install/schedule.js +114 -0
  148. package/dist/install/schedule.js.map +1 -0
  149. package/dist/install/setup-wizard.d.ts +40 -0
  150. package/dist/install/setup-wizard.d.ts.map +1 -0
  151. package/dist/install/setup-wizard.js +489 -0
  152. package/dist/install/setup-wizard.js.map +1 -0
  153. package/dist/lib/date.d.ts +54 -0
  154. package/dist/lib/date.d.ts.map +1 -0
  155. package/dist/lib/date.js +85 -0
  156. package/dist/lib/date.js.map +1 -0
  157. package/dist/metrics/anti-patterns.d.ts +62 -0
  158. package/dist/metrics/anti-patterns.d.ts.map +1 -0
  159. package/dist/metrics/anti-patterns.js +301 -0
  160. package/dist/metrics/anti-patterns.js.map +1 -0
  161. package/dist/metrics/api-failure-tracker.d.ts +82 -0
  162. package/dist/metrics/api-failure-tracker.d.ts.map +1 -0
  163. package/dist/metrics/api-failure-tracker.js +202 -0
  164. package/dist/metrics/api-failure-tracker.js.map +1 -0
  165. package/dist/metrics/budget-tracker.d.ts +60 -0
  166. package/dist/metrics/budget-tracker.d.ts.map +1 -0
  167. package/dist/metrics/budget-tracker.js +130 -0
  168. package/dist/metrics/budget-tracker.js.map +1 -0
  169. package/dist/metrics/claudemd-tracker.d.ts +108 -0
  170. package/dist/metrics/claudemd-tracker.d.ts.map +1 -0
  171. package/dist/metrics/claudemd-tracker.js +337 -0
  172. package/dist/metrics/claudemd-tracker.js.map +1 -0
  173. package/dist/metrics/collaboration-profile.d.ts +65 -0
  174. package/dist/metrics/collaboration-profile.d.ts.map +1 -0
  175. package/dist/metrics/collaboration-profile.js +231 -0
  176. package/dist/metrics/collaboration-profile.js.map +1 -0
  177. package/dist/metrics/context-composition-tracker.d.ts +74 -0
  178. package/dist/metrics/context-composition-tracker.d.ts.map +1 -0
  179. package/dist/metrics/context-composition-tracker.js +202 -0
  180. package/dist/metrics/context-composition-tracker.js.map +1 -0
  181. package/dist/metrics/context-tracker.d.ts +78 -0
  182. package/dist/metrics/context-tracker.d.ts.map +1 -0
  183. package/dist/metrics/context-tracker.js +222 -0
  184. package/dist/metrics/context-tracker.js.map +1 -0
  185. package/dist/metrics/context-window-tracker.d.ts +18 -0
  186. package/dist/metrics/context-window-tracker.d.ts.map +1 -0
  187. package/dist/metrics/context-window-tracker.js +35 -0
  188. package/dist/metrics/context-window-tracker.js.map +1 -0
  189. package/dist/metrics/cost-forecast.d.ts +36 -0
  190. package/dist/metrics/cost-forecast.d.ts.map +1 -0
  191. package/dist/metrics/cost-forecast.js +91 -0
  192. package/dist/metrics/cost-forecast.js.map +1 -0
  193. package/dist/metrics/cost-per-outcome.d.ts +102 -0
  194. package/dist/metrics/cost-per-outcome.d.ts.map +1 -0
  195. package/dist/metrics/cost-per-outcome.js +266 -0
  196. package/dist/metrics/cost-per-outcome.js.map +1 -0
  197. package/dist/metrics/cost-tracker.d.ts +78 -0
  198. package/dist/metrics/cost-tracker.d.ts.map +1 -0
  199. package/dist/metrics/cost-tracker.js +169 -0
  200. package/dist/metrics/cost-tracker.js.map +1 -0
  201. package/dist/metrics/decision-tracker.d.ts +49 -0
  202. package/dist/metrics/decision-tracker.d.ts.map +1 -0
  203. package/dist/metrics/decision-tracker.js +161 -0
  204. package/dist/metrics/decision-tracker.js.map +1 -0
  205. package/dist/metrics/efficiency-score.d.ts +80 -0
  206. package/dist/metrics/efficiency-score.d.ts.map +1 -0
  207. package/dist/metrics/efficiency-score.js +219 -0
  208. package/dist/metrics/efficiency-score.js.map +1 -0
  209. package/dist/metrics/git-efficiency-tracker.d.ts +165 -0
  210. package/dist/metrics/git-efficiency-tracker.d.ts.map +1 -0
  211. package/dist/metrics/git-efficiency-tracker.js +1056 -0
  212. package/dist/metrics/git-efficiency-tracker.js.map +1 -0
  213. package/dist/metrics/index.d.ts +26 -0
  214. package/dist/metrics/index.d.ts.map +1 -0
  215. package/dist/metrics/index.js +14 -0
  216. package/dist/metrics/index.js.map +1 -0
  217. package/dist/metrics/instruction-drift-tracker.d.ts +69 -0
  218. package/dist/metrics/instruction-drift-tracker.d.ts.map +1 -0
  219. package/dist/metrics/instruction-drift-tracker.js +213 -0
  220. package/dist/metrics/instruction-drift-tracker.js.map +1 -0
  221. package/dist/metrics/latency-decomposition.d.ts +50 -0
  222. package/dist/metrics/latency-decomposition.d.ts.map +1 -0
  223. package/dist/metrics/latency-decomposition.js +112 -0
  224. package/dist/metrics/latency-decomposition.js.map +1 -0
  225. package/dist/metrics/latency-tracker.d.ts +33 -0
  226. package/dist/metrics/latency-tracker.d.ts.map +1 -0
  227. package/dist/metrics/latency-tracker.js +93 -0
  228. package/dist/metrics/latency-tracker.js.map +1 -0
  229. package/dist/metrics/live-session-registry.d.ts +29 -0
  230. package/dist/metrics/live-session-registry.d.ts.map +1 -0
  231. package/dist/metrics/live-session-registry.js +103 -0
  232. package/dist/metrics/live-session-registry.js.map +1 -0
  233. package/dist/metrics/model-usage-tracker.d.ts +21 -0
  234. package/dist/metrics/model-usage-tracker.d.ts.map +1 -0
  235. package/dist/metrics/model-usage-tracker.js +53 -0
  236. package/dist/metrics/model-usage-tracker.js.map +1 -0
  237. package/dist/metrics/percentile.d.ts +5 -0
  238. package/dist/metrics/percentile.d.ts.map +1 -0
  239. package/dist/metrics/percentile.js +10 -0
  240. package/dist/metrics/percentile.js.map +1 -0
  241. package/dist/metrics/personal-coach.d.ts +47 -0
  242. package/dist/metrics/personal-coach.d.ts.map +1 -0
  243. package/dist/metrics/personal-coach.js +241 -0
  244. package/dist/metrics/personal-coach.js.map +1 -0
  245. package/dist/metrics/prompt-feedback.d.ts +75 -0
  246. package/dist/metrics/prompt-feedback.d.ts.map +1 -0
  247. package/dist/metrics/prompt-feedback.js +286 -0
  248. package/dist/metrics/prompt-feedback.js.map +1 -0
  249. package/dist/metrics/proxy-metrics.d.ts +54 -0
  250. package/dist/metrics/proxy-metrics.d.ts.map +1 -0
  251. package/dist/metrics/proxy-metrics.js +228 -0
  252. package/dist/metrics/proxy-metrics.js.map +1 -0
  253. package/dist/metrics/quality-proxy-tracker.d.ts +51 -0
  254. package/dist/metrics/quality-proxy-tracker.d.ts.map +1 -0
  255. package/dist/metrics/quality-proxy-tracker.js +162 -0
  256. package/dist/metrics/quality-proxy-tracker.js.map +1 -0
  257. package/dist/metrics/recommendation-engine.d.ts +72 -0
  258. package/dist/metrics/recommendation-engine.d.ts.map +1 -0
  259. package/dist/metrics/recommendation-engine.js +207 -0
  260. package/dist/metrics/recommendation-engine.js.map +1 -0
  261. package/dist/metrics/retry-detector.d.ts +43 -0
  262. package/dist/metrics/retry-detector.d.ts.map +1 -0
  263. package/dist/metrics/retry-detector.js +179 -0
  264. package/dist/metrics/retry-detector.js.map +1 -0
  265. package/dist/metrics/session-tracker.d.ts +75 -0
  266. package/dist/metrics/session-tracker.d.ts.map +1 -0
  267. package/dist/metrics/session-tracker.js +249 -0
  268. package/dist/metrics/session-tracker.js.map +1 -0
  269. package/dist/metrics/task-completion-tracker.d.ts +15 -0
  270. package/dist/metrics/task-completion-tracker.d.ts.map +1 -0
  271. package/dist/metrics/task-completion-tracker.js +27 -0
  272. package/dist/metrics/task-completion-tracker.js.map +1 -0
  273. package/dist/metrics/task-detector.d.ts +84 -0
  274. package/dist/metrics/task-detector.d.ts.map +1 -0
  275. package/dist/metrics/task-detector.js +302 -0
  276. package/dist/metrics/task-detector.js.map +1 -0
  277. package/dist/metrics/tool-selection-scorer.d.ts +39 -0
  278. package/dist/metrics/tool-selection-scorer.d.ts.map +1 -0
  279. package/dist/metrics/tool-selection-scorer.js +193 -0
  280. package/dist/metrics/tool-selection-scorer.js.map +1 -0
  281. package/dist/metrics/trend-analyzer.d.ts +92 -0
  282. package/dist/metrics/trend-analyzer.d.ts.map +1 -0
  283. package/dist/metrics/trend-analyzer.js +293 -0
  284. package/dist/metrics/trend-analyzer.js.map +1 -0
  285. package/dist/metrics/turn-cost-attributor.d.ts +41 -0
  286. package/dist/metrics/turn-cost-attributor.d.ts.map +1 -0
  287. package/dist/metrics/turn-cost-attributor.js +118 -0
  288. package/dist/metrics/turn-cost-attributor.js.map +1 -0
  289. package/dist/metrics/turn-tracker.d.ts +49 -0
  290. package/dist/metrics/turn-tracker.d.ts.map +1 -0
  291. package/dist/metrics/turn-tracker.js +192 -0
  292. package/dist/metrics/turn-tracker.js.map +1 -0
  293. package/dist/platforms/amazon-q-adapter.d.ts +10 -0
  294. package/dist/platforms/amazon-q-adapter.d.ts.map +1 -0
  295. package/dist/platforms/amazon-q-adapter.js +75 -0
  296. package/dist/platforms/amazon-q-adapter.js.map +1 -0
  297. package/dist/platforms/claude-code-adapter.d.ts +10 -0
  298. package/dist/platforms/claude-code-adapter.d.ts.map +1 -0
  299. package/dist/platforms/claude-code-adapter.js +48 -0
  300. package/dist/platforms/claude-code-adapter.js.map +1 -0
  301. package/dist/platforms/continue-adapter.d.ts +10 -0
  302. package/dist/platforms/continue-adapter.d.ts.map +1 -0
  303. package/dist/platforms/continue-adapter.js +73 -0
  304. package/dist/platforms/continue-adapter.js.map +1 -0
  305. package/dist/platforms/copilot-adapter.d.ts +37 -0
  306. package/dist/platforms/copilot-adapter.d.ts.map +1 -0
  307. package/dist/platforms/copilot-adapter.js +66 -0
  308. package/dist/platforms/copilot-adapter.js.map +1 -0
  309. package/dist/platforms/cursor-adapter.d.ts +10 -0
  310. package/dist/platforms/cursor-adapter.d.ts.map +1 -0
  311. package/dist/platforms/cursor-adapter.js +60 -0
  312. package/dist/platforms/cursor-adapter.js.map +1 -0
  313. package/dist/platforms/generic-mcp-adapter.d.ts +113 -0
  314. package/dist/platforms/generic-mcp-adapter.d.ts.map +1 -0
  315. package/dist/platforms/generic-mcp-adapter.js +139 -0
  316. package/dist/platforms/generic-mcp-adapter.js.map +1 -0
  317. package/dist/platforms/index.d.ts +15 -0
  318. package/dist/platforms/index.d.ts.map +1 -0
  319. package/dist/platforms/index.js +12 -0
  320. package/dist/platforms/index.js.map +1 -0
  321. package/dist/platforms/platform-registry.d.ts +11 -0
  322. package/dist/platforms/platform-registry.d.ts.map +1 -0
  323. package/dist/platforms/platform-registry.js +54 -0
  324. package/dist/platforms/platform-registry.js.map +1 -0
  325. package/dist/platforms/types.d.ts +36 -0
  326. package/dist/platforms/types.d.ts.map +1 -0
  327. package/dist/platforms/types.js +2 -0
  328. package/dist/platforms/types.js.map +1 -0
  329. package/dist/platforms/windsurf-adapter.d.ts +10 -0
  330. package/dist/platforms/windsurf-adapter.d.ts.map +1 -0
  331. package/dist/platforms/windsurf-adapter.js +63 -0
  332. package/dist/platforms/windsurf-adapter.js.map +1 -0
  333. package/dist/platforms/zed-adapter.d.ts +10 -0
  334. package/dist/platforms/zed-adapter.d.ts.map +1 -0
  335. package/dist/platforms/zed-adapter.js +72 -0
  336. package/dist/platforms/zed-adapter.js.map +1 -0
  337. package/dist/proxy/index.d.ts +7 -0
  338. package/dist/proxy/index.d.ts.map +1 -0
  339. package/dist/proxy/index.js +5 -0
  340. package/dist/proxy/index.js.map +1 -0
  341. package/dist/proxy/otlp-receiver.d.ts +28 -0
  342. package/dist/proxy/otlp-receiver.d.ts.map +1 -0
  343. package/dist/proxy/otlp-receiver.js +319 -0
  344. package/dist/proxy/otlp-receiver.js.map +1 -0
  345. package/dist/proxy/proxy-manager.d.ts +47 -0
  346. package/dist/proxy/proxy-manager.d.ts.map +1 -0
  347. package/dist/proxy/proxy-manager.js +338 -0
  348. package/dist/proxy/proxy-manager.js.map +1 -0
  349. package/dist/proxy/types.d.ts +72 -0
  350. package/dist/proxy/types.d.ts.map +1 -0
  351. package/dist/proxy/types.js +33 -0
  352. package/dist/proxy/types.js.map +1 -0
  353. package/dist/proxy/upstream-http.d.ts +26 -0
  354. package/dist/proxy/upstream-http.d.ts.map +1 -0
  355. package/dist/proxy/upstream-http.js +209 -0
  356. package/dist/proxy/upstream-http.js.map +1 -0
  357. package/dist/proxy/upstream-stdio.d.ts +25 -0
  358. package/dist/proxy/upstream-stdio.d.ts.map +1 -0
  359. package/dist/proxy/upstream-stdio.js +256 -0
  360. package/dist/proxy/upstream-stdio.js.map +1 -0
  361. package/dist/security/audit-trail.d.ts +74 -0
  362. package/dist/security/audit-trail.d.ts.map +1 -0
  363. package/dist/security/audit-trail.js +338 -0
  364. package/dist/security/audit-trail.js.map +1 -0
  365. package/dist/security/index.d.ts +5 -0
  366. package/dist/security/index.d.ts.map +1 -0
  367. package/dist/security/index.js +4 -0
  368. package/dist/security/index.js.map +1 -0
  369. package/dist/security/ssrf.d.ts +2 -0
  370. package/dist/security/ssrf.d.ts.map +1 -0
  371. package/dist/security/ssrf.js +126 -0
  372. package/dist/security/ssrf.js.map +1 -0
  373. package/dist/server.d.ts +14 -0
  374. package/dist/server.d.ts.map +1 -0
  375. package/dist/server.js +117 -0
  376. package/dist/server.js.map +1 -0
  377. package/dist/shared/__test-utils__/log-output.d.ts +49 -0
  378. package/dist/shared/__test-utils__/log-output.d.ts.map +1 -0
  379. package/dist/shared/__test-utils__/log-output.js +38 -0
  380. package/dist/shared/__test-utils__/log-output.js.map +1 -0
  381. package/dist/shared/config.d.ts +56 -0
  382. package/dist/shared/config.d.ts.map +1 -0
  383. package/dist/shared/config.js +290 -0
  384. package/dist/shared/config.js.map +1 -0
  385. package/dist/shared/errors.d.ts +139 -0
  386. package/dist/shared/errors.d.ts.map +1 -0
  387. package/dist/shared/errors.js +406 -0
  388. package/dist/shared/errors.js.map +1 -0
  389. package/dist/shared/events/factory.d.ts +143 -0
  390. package/dist/shared/events/factory.d.ts.map +1 -0
  391. package/dist/shared/events/factory.js +351 -0
  392. package/dist/shared/events/factory.js.map +1 -0
  393. package/dist/shared/events/index.d.ts +6 -0
  394. package/dist/shared/events/index.d.ts.map +1 -0
  395. package/dist/shared/events/index.js +3 -0
  396. package/dist/shared/events/index.js.map +1 -0
  397. package/dist/shared/events/serialize.d.ts +87 -0
  398. package/dist/shared/events/serialize.d.ts.map +1 -0
  399. package/dist/shared/events/serialize.js +510 -0
  400. package/dist/shared/events/serialize.js.map +1 -0
  401. package/dist/shared/events/types.d.ts +139 -0
  402. package/dist/shared/events/types.d.ts.map +1 -0
  403. package/dist/shared/events/types.js +2 -0
  404. package/dist/shared/events/types.js.map +1 -0
  405. package/dist/shared/harvest/event-buffer.d.ts +59 -0
  406. package/dist/shared/harvest/event-buffer.d.ts.map +1 -0
  407. package/dist/shared/harvest/event-buffer.js +100 -0
  408. package/dist/shared/harvest/event-buffer.js.map +1 -0
  409. package/dist/shared/harvest/harvest-scheduler.d.ts +200 -0
  410. package/dist/shared/harvest/harvest-scheduler.d.ts.map +1 -0
  411. package/dist/shared/harvest/harvest-scheduler.js +647 -0
  412. package/dist/shared/harvest/harvest-scheduler.js.map +1 -0
  413. package/dist/shared/harvest/index.d.ts +7 -0
  414. package/dist/shared/harvest/index.d.ts.map +1 -0
  415. package/dist/shared/harvest/index.js +4 -0
  416. package/dist/shared/harvest/index.js.map +1 -0
  417. package/dist/shared/harvest/metric-aggregator.d.ts +115 -0
  418. package/dist/shared/harvest/metric-aggregator.d.ts.map +1 -0
  419. package/dist/shared/harvest/metric-aggregator.js +247 -0
  420. package/dist/shared/harvest/metric-aggregator.js.map +1 -0
  421. package/dist/shared/index.d.ts +22 -0
  422. package/dist/shared/index.d.ts.map +1 -0
  423. package/dist/shared/index.js +13 -0
  424. package/dist/shared/index.js.map +1 -0
  425. package/dist/shared/logger.d.ts +57 -0
  426. package/dist/shared/logger.d.ts.map +1 -0
  427. package/dist/shared/logger.js +166 -0
  428. package/dist/shared/logger.js.map +1 -0
  429. package/dist/shared/pricing-data.d.ts +4 -0
  430. package/dist/shared/pricing-data.d.ts.map +1 -0
  431. package/dist/shared/pricing-data.js +473 -0
  432. package/dist/shared/pricing-data.js.map +1 -0
  433. package/dist/shared/pricing.d.ts +148 -0
  434. package/dist/shared/pricing.d.ts.map +1 -0
  435. package/dist/shared/pricing.js +528 -0
  436. package/dist/shared/pricing.js.map +1 -0
  437. package/dist/shared/redact.d.ts +33 -0
  438. package/dist/shared/redact.d.ts.map +1 -0
  439. package/dist/shared/redact.js +110 -0
  440. package/dist/shared/redact.js.map +1 -0
  441. package/dist/shared/timing.d.ts +96 -0
  442. package/dist/shared/timing.d.ts.map +1 -0
  443. package/dist/shared/timing.js +173 -0
  444. package/dist/shared/timing.js.map +1 -0
  445. package/dist/shared/tokens.d.ts +145 -0
  446. package/dist/shared/tokens.d.ts.map +1 -0
  447. package/dist/shared/tokens.js +492 -0
  448. package/dist/shared/tokens.js.map +1 -0
  449. package/dist/shared/transport/events-api.d.ts +14 -0
  450. package/dist/shared/transport/events-api.d.ts.map +1 -0
  451. package/dist/shared/transport/events-api.js +29 -0
  452. package/dist/shared/transport/events-api.js.map +1 -0
  453. package/dist/shared/transport/http-client.d.ts +49 -0
  454. package/dist/shared/transport/http-client.d.ts.map +1 -0
  455. package/dist/shared/transport/http-client.js +381 -0
  456. package/dist/shared/transport/http-client.js.map +1 -0
  457. package/dist/shared/transport/index.d.ts +10 -0
  458. package/dist/shared/transport/index.d.ts.map +1 -0
  459. package/dist/shared/transport/index.js +6 -0
  460. package/dist/shared/transport/index.js.map +1 -0
  461. package/dist/shared/transport/logs-api.d.ts +29 -0
  462. package/dist/shared/transport/logs-api.d.ts.map +1 -0
  463. package/dist/shared/transport/logs-api.js +40 -0
  464. package/dist/shared/transport/logs-api.js.map +1 -0
  465. package/dist/shared/transport/metric-api.d.ts +9 -0
  466. package/dist/shared/transport/metric-api.d.ts.map +1 -0
  467. package/dist/shared/transport/metric-api.js +39 -0
  468. package/dist/shared/transport/metric-api.js.map +1 -0
  469. package/dist/shared/transport/otlp-event-bridge.d.ts +22 -0
  470. package/dist/shared/transport/otlp-event-bridge.d.ts.map +1 -0
  471. package/dist/shared/transport/otlp-event-bridge.js +50 -0
  472. package/dist/shared/transport/otlp-event-bridge.js.map +1 -0
  473. package/dist/shared/transport/otlp-shared.d.ts +14 -0
  474. package/dist/shared/transport/otlp-shared.d.ts.map +1 -0
  475. package/dist/shared/transport/otlp-shared.js +49 -0
  476. package/dist/shared/transport/otlp-shared.js.map +1 -0
  477. package/dist/shared/transport/otlp-transport.d.ts +58 -0
  478. package/dist/shared/transport/otlp-transport.d.ts.map +1 -0
  479. package/dist/shared/transport/otlp-transport.js +236 -0
  480. package/dist/shared/transport/otlp-transport.js.map +1 -0
  481. package/dist/shared/transport/types.d.ts +129 -0
  482. package/dist/shared/transport/types.d.ts.map +1 -0
  483. package/dist/shared/transport/types.js +2 -0
  484. package/dist/shared/transport/types.js.map +1 -0
  485. package/dist/shared/version.d.ts +2 -0
  486. package/dist/shared/version.d.ts.map +1 -0
  487. package/dist/shared/version.js +2 -0
  488. package/dist/shared/version.js.map +1 -0
  489. package/dist/storage/index.d.ts +7 -0
  490. package/dist/storage/index.d.ts.map +1 -0
  491. package/dist/storage/index.js +4 -0
  492. package/dist/storage/index.js.map +1 -0
  493. package/dist/storage/local-store.d.ts +153 -0
  494. package/dist/storage/local-store.d.ts.map +1 -0
  495. package/dist/storage/local-store.js +719 -0
  496. package/dist/storage/local-store.js.map +1 -0
  497. package/dist/storage/retention.d.ts +2 -0
  498. package/dist/storage/retention.d.ts.map +1 -0
  499. package/dist/storage/retention.js +53 -0
  500. package/dist/storage/retention.js.map +1 -0
  501. package/dist/storage/session-store.d.ts +97 -0
  502. package/dist/storage/session-store.d.ts.map +1 -0
  503. package/dist/storage/session-store.js +391 -0
  504. package/dist/storage/session-store.js.map +1 -0
  505. package/dist/storage/types.d.ts +64 -0
  506. package/dist/storage/types.d.ts.map +1 -0
  507. package/dist/storage/types.js +2 -0
  508. package/dist/storage/types.js.map +1 -0
  509. package/dist/storage/weekly-summary.d.ts +61 -0
  510. package/dist/storage/weekly-summary.d.ts.map +1 -0
  511. package/dist/storage/weekly-summary.js +243 -0
  512. package/dist/storage/weekly-summary.js.map +1 -0
  513. package/dist/tools/analytics-tools.d.ts +101 -0
  514. package/dist/tools/analytics-tools.d.ts.map +1 -0
  515. package/dist/tools/analytics-tools.js +71 -0
  516. package/dist/tools/analytics-tools.js.map +1 -0
  517. package/dist/tools/cost-tools.d.ts +121 -0
  518. package/dist/tools/cost-tools.d.ts.map +1 -0
  519. package/dist/tools/cost-tools.js +174 -0
  520. package/dist/tools/cost-tools.js.map +1 -0
  521. package/dist/tools/cross-session-tools.d.ts +376 -0
  522. package/dist/tools/cross-session-tools.d.ts.map +1 -0
  523. package/dist/tools/cross-session-tools.js +820 -0
  524. package/dist/tools/cross-session-tools.js.map +1 -0
  525. package/dist/tools/extended-analytics-tools.d.ts +164 -0
  526. package/dist/tools/extended-analytics-tools.d.ts.map +1 -0
  527. package/dist/tools/extended-analytics-tools.js +121 -0
  528. package/dist/tools/extended-analytics-tools.js.map +1 -0
  529. package/dist/tools/index.d.ts +7 -0
  530. package/dist/tools/index.d.ts.map +1 -0
  531. package/dist/tools/index.js +4 -0
  532. package/dist/tools/index.js.map +1 -0
  533. package/dist/tools/session-stats.d.ts +162 -0
  534. package/dist/tools/session-stats.d.ts.map +1 -0
  535. package/dist/tools/session-stats.js +1054 -0
  536. package/dist/tools/session-stats.js.map +1 -0
  537. package/dist/tools/workflow-tools.d.ts +126 -0
  538. package/dist/tools/workflow-tools.d.ts.map +1 -0
  539. package/dist/tools/workflow-tools.js +274 -0
  540. package/dist/tools/workflow-tools.js.map +1 -0
  541. package/dist/tracing/mcp-tracer.d.ts +4 -0
  542. package/dist/tracing/mcp-tracer.d.ts.map +1 -0
  543. package/dist/tracing/mcp-tracer.js +14 -0
  544. package/dist/tracing/mcp-tracer.js.map +1 -0
  545. package/dist/tracing/session-span.d.ts +14 -0
  546. package/dist/tracing/session-span.d.ts.map +1 -0
  547. package/dist/tracing/session-span.js +53 -0
  548. package/dist/tracing/session-span.js.map +1 -0
  549. package/dist/tracing/task-span-tracker.d.ts +11 -0
  550. package/dist/tracing/task-span-tracker.d.ts.map +1 -0
  551. package/dist/tracing/task-span-tracker.js +59 -0
  552. package/dist/tracing/task-span-tracker.js.map +1 -0
  553. package/dist/tracing/tool-call-span.d.ts +4 -0
  554. package/dist/tracing/tool-call-span.d.ts.map +1 -0
  555. package/dist/tracing/tool-call-span.js +60 -0
  556. package/dist/tracing/tool-call-span.js.map +1 -0
  557. package/dist/transport/index.d.ts +3 -0
  558. package/dist/transport/index.d.ts.map +1 -0
  559. package/dist/transport/index.js +2 -0
  560. package/dist/transport/index.js.map +1 -0
  561. package/dist/transport/log-ingest.d.ts +42 -0
  562. package/dist/transport/log-ingest.d.ts.map +1 -0
  563. package/dist/transport/log-ingest.js +151 -0
  564. package/dist/transport/log-ingest.js.map +1 -0
  565. package/dist/transport/nr-ingest.d.ts +171 -0
  566. package/dist/transport/nr-ingest.d.ts.map +1 -0
  567. package/dist/transport/nr-ingest.js +659 -0
  568. package/dist/transport/nr-ingest.js.map +1 -0
  569. package/dist/types.d.ts +45 -0
  570. package/dist/types.d.ts.map +1 -0
  571. package/dist/types.js +2 -0
  572. package/dist/types.js.map +1 -0
  573. package/dist/web/assets/index-BrL281N-.css +2 -0
  574. package/dist/web/assets/index-CcaYZzXm.js +42 -0
  575. package/dist/web/favicon.svg +15 -0
  576. package/dist/web/index.html +15 -0
  577. package/examples/local-alert-rules.json +106 -0
  578. package/package.json +125 -1
@@ -0,0 +1,719 @@
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, readdirSync, statSync, } from 'node:fs';
2
+ import { resolve, join, sep } from 'node:path';
3
+ import { createLogger } from '../shared/index.js';
4
+ const logger = createLogger('local-store');
5
+ const SESSION_ID_RE = /^[a-zA-Z0-9_-]{1,128}$/;
6
+ /**
7
+ * A buffer file is treated as orphan when its mtime is older than this AND
8
+ * no live heartbeat is present. Five minutes is well past any normal harvest
9
+ * tick (poll interval is sub-second) so a healthy MCP that's just sitting
10
+ * idle without tool calls won't trip the threshold.
11
+ */
12
+ const ORPHAN_BUFFER_MTIME_MS = 5 * 60 * 1000;
13
+ /**
14
+ * Liveness probe via signal 0 — POSIX sends no signal but performs the
15
+ * permission/existence checks. Returns true iff the PID names a live process
16
+ * we have rights to signal. ESRCH = dead, EPERM = alive (different uid),
17
+ * anything else (e.g. EINVAL on a malformed PID) = treat as dead.
18
+ */
19
+ function isPidAlive(pid) {
20
+ if (!Number.isFinite(pid) || pid <= 0)
21
+ return false;
22
+ try {
23
+ process.kill(pid, 0);
24
+ return true;
25
+ }
26
+ catch (err) {
27
+ const code = err.code;
28
+ if (code === 'EPERM')
29
+ return true; // exists, owned by another uid
30
+ return false;
31
+ }
32
+ }
33
+ export class LocalStore {
34
+ storagePath;
35
+ bufferPath;
36
+ sessionId;
37
+ /**
38
+ * @param storagePath The base storage directory (e.g. ~/.newrelic-preflight).
39
+ * @param bufferPathOrSessionId Either an explicit absolute buffer path
40
+ * (legacy / test override; takes precedence over sessionId) or a sessionId
41
+ * that scopes the per-session buffer file (`buffer-<sessionId>.jsonl`).
42
+ * Pass `undefined` for fan-out drain mode (`--local`) where there is no
43
+ * single owning session — `drainBuffer()` will then return [] and callers
44
+ * should use `drainAllBuffers()` instead.
45
+ * @param sessionId Optional sessionId for scoping per-session buffers.
46
+ * Used when an explicit bufferPath is also provided (rare — the explicit
47
+ * path still wins for the buffer location, sessionId is then ignored).
48
+ */
49
+ constructor(storagePath, bufferPathOrSessionId, sessionId) {
50
+ this.storagePath = storagePath;
51
+ let resolvedBufferPath;
52
+ let resolvedSessionId = null;
53
+ if (bufferPathOrSessionId !== undefined && bufferPathOrSessionId.includes(sep)) {
54
+ // Treat anything containing a path separator as an explicit path
55
+ // override. Tests and `NEW_RELIC_AI_MCP_BUFFER_PATH` use this form.
56
+ resolvedBufferPath = bufferPathOrSessionId;
57
+ resolvedSessionId =
58
+ typeof sessionId === 'string' && SESSION_ID_RE.test(sessionId) ? sessionId : null;
59
+ }
60
+ else if (bufferPathOrSessionId !== undefined && SESSION_ID_RE.test(bufferPathOrSessionId)) {
61
+ // Per-session buffer scoping (Fix 3 happy path).
62
+ resolvedBufferPath = resolve(storagePath, `buffer-${bufferPathOrSessionId}.jsonl`);
63
+ resolvedSessionId = bufferPathOrSessionId;
64
+ }
65
+ else if (bufferPathOrSessionId === undefined) {
66
+ // No-session mode: --local drain-all, or pre-resolution startup. Leave
67
+ // bufferPath at the legacy default so single-call drainBuffer() doesn't
68
+ // crash on undefined; drainAllBuffers() is the right entry point here.
69
+ resolvedBufferPath = resolve(storagePath, 'buffer.jsonl');
70
+ }
71
+ else {
72
+ // Bare value that's neither a path nor a valid sessionId — refuse to
73
+ // construct rather than silently routing live data to
74
+ // buffer-unknown.jsonl, which the MCP's session-scoped drainBuffer()
75
+ // would never read and which gcOrphanBuffers() would archive after 5
76
+ // minutes of mtime staleness. The caller (src/index.ts) is responsible
77
+ // for validating sessionTraceId against SESSION_ID_RE before passing it
78
+ // here. session-resolver already enforces this; if this throws it's a
79
+ // real bug worth surfacing.
80
+ throw new Error(`LocalStore: invalid sessionId (must match SESSION_ID_RE /^[a-zA-Z0-9_-]{1,128}$/): ${JSON.stringify(bufferPathOrSessionId)}`);
81
+ }
82
+ this.bufferPath = resolvedBufferPath;
83
+ this.sessionId = resolvedSessionId;
84
+ }
85
+ initialize() {
86
+ const dirs = [
87
+ this.storagePath,
88
+ resolve(this.storagePath, 'sessions'),
89
+ resolve(this.storagePath, 'weekly_summaries'),
90
+ resolve(this.storagePath, 'audit'),
91
+ ];
92
+ for (const dir of dirs) {
93
+ if (!existsSync(dir)) {
94
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
95
+ }
96
+ }
97
+ logger.debug('Storage initialized', { path: this.storagePath });
98
+ }
99
+ /**
100
+ * Append a hook event as a JSON line to the buffer file.
101
+ * Uses appendFileSync for minimal latency (<5ms budget).
102
+ */
103
+ appendToBuffer(event) {
104
+ try {
105
+ appendFileSync(this.bufferPath, JSON.stringify(event) + '\n', { mode: 0o600 });
106
+ }
107
+ catch (err) {
108
+ // Never block the caller — log and move on
109
+ logger.warn('Failed to append to buffer', { error: String(err) });
110
+ }
111
+ }
112
+ /**
113
+ * Atomically drain all events from the buffer file.
114
+ * Renames the file to a temp path (atomic on POSIX), reads it, then deletes.
115
+ * This avoids data loss from concurrent hook writes during drain.
116
+ */
117
+ drainBuffer() {
118
+ return this.drainPath(this.bufferPath);
119
+ }
120
+ /**
121
+ * Drain every per-session buffer file under the storage path. Used by
122
+ * `--local` standalone mode where the dashboard owner sees all live
123
+ * sessions' events. Each file is drained atomically (rename-then-read) so a
124
+ * concurrent writer for that session can't lose events; orphan files (whose
125
+ * MCP isn't running) are picked up too.
126
+ */
127
+ drainAllBuffers() {
128
+ const all = [];
129
+ let entries;
130
+ try {
131
+ if (!existsSync(this.storagePath))
132
+ return [];
133
+ entries = readdirSync(this.storagePath);
134
+ }
135
+ catch (err) {
136
+ logger.warn('Failed to enumerate storage path for drainAllBuffers', { error: String(err) });
137
+ return [];
138
+ }
139
+ for (const name of entries) {
140
+ // Per-session: buffer-<id>.jsonl. Also pick up the legacy shared
141
+ // buffer.jsonl so a freshly-upgraded user's pre-Fix-3 events still flow.
142
+ if (!name.endsWith('.jsonl'))
143
+ continue;
144
+ if (name !== 'buffer.jsonl' && !name.startsWith('buffer-'))
145
+ continue;
146
+ const drained = this.drainPath(resolve(this.storagePath, name));
147
+ if (drained.length > 0) {
148
+ for (const event of drained)
149
+ all.push(event);
150
+ }
151
+ }
152
+ return all;
153
+ }
154
+ /**
155
+ * Read every per-session buffer file under the storage path WITHOUT draining
156
+ * (no rename, no unlink). Used by the dashboard owner's
157
+ * `/api/sessions/today/aggregate` endpoint, which surfaces a global view
158
+ * across every live session — its own session_id-scoped drainBuffer() only
159
+ * covers its own events, so we need a read-only fan-out for cross-session
160
+ * KPIs.
161
+ *
162
+ * Torn-line handling: the LAST non-empty line is the only line that can
163
+ * legitimately be torn (a concurrent appender's incomplete write). If it
164
+ * fails to parse we drop it silently. Any earlier line that fails to parse
165
+ * is real corruption — events >PIPE_BUF (typically 4 KiB) interleaved by
166
+ * concurrent appenders, or an on-disk write that flushed partially. We log
167
+ * a WARN so the loss is visible rather than silently dropped.
168
+ *
169
+ * The legacy shared `buffer.jsonl` is included so an upgrading user with
170
+ * unmigrated events still appears in aggregate views before the next
171
+ * `migrateLegacyBuffer()` runs.
172
+ */
173
+ peekAllBuffers() {
174
+ const all = [];
175
+ let entries;
176
+ try {
177
+ if (!existsSync(this.storagePath))
178
+ return [];
179
+ entries = readdirSync(this.storagePath);
180
+ }
181
+ catch (err) {
182
+ logger.warn('Failed to enumerate storage path for peekAllBuffers', { error: String(err) });
183
+ return [];
184
+ }
185
+ for (const name of entries) {
186
+ if (!name.endsWith('.jsonl'))
187
+ continue;
188
+ if (name !== 'buffer.jsonl' && !name.startsWith('buffer-'))
189
+ continue;
190
+ const path = resolve(this.storagePath, name);
191
+ let raw;
192
+ try {
193
+ raw = readFileSync(path, 'utf-8');
194
+ }
195
+ catch {
196
+ continue;
197
+ }
198
+ if (!raw.trim())
199
+ continue;
200
+ // Find the index of the last non-empty line so we can distinguish a
201
+ // legitimately-torn tail from real mid-file corruption.
202
+ const lines = raw.split('\n');
203
+ let lastNonEmptyIdx = -1;
204
+ for (let i = lines.length - 1; i >= 0; i--) {
205
+ if (lines[i].trim()) {
206
+ lastNonEmptyIdx = i;
207
+ break;
208
+ }
209
+ }
210
+ for (let i = 0; i < lines.length; i++) {
211
+ const line = lines[i];
212
+ if (!line.trim())
213
+ continue;
214
+ try {
215
+ all.push(JSON.parse(line));
216
+ }
217
+ catch {
218
+ if (i === lastNonEmptyIdx) {
219
+ // Torn-tail race against a concurrent appender — expected.
220
+ continue;
221
+ }
222
+ // Mid-file parse failure means real data corruption — surface it.
223
+ logger.warn('peekAllBuffers: dropping malformed mid-file line', {
224
+ file: name,
225
+ lineIndex: i,
226
+ preview: line.slice(0, 100),
227
+ eventsSoFar: all.length,
228
+ });
229
+ }
230
+ }
231
+ }
232
+ return all;
233
+ }
234
+ /**
235
+ * Write a heartbeat file `active-<sessionId>.pid` containing this process's
236
+ * PID. Used by the dashboard owner's GC pass to determine which buffer
237
+ * files still have a live owner. The MCP should call this once after
238
+ * resolving its session_id, and remove it on graceful shutdown via
239
+ * `removeHeartbeat()`.
240
+ *
241
+ * No-op if the LocalStore is not bound to a sessionId (e.g. --local mode).
242
+ */
243
+ writeHeartbeat(pid = process.pid) {
244
+ if (!this.sessionId)
245
+ return;
246
+ if (!Number.isFinite(pid) || pid <= 0)
247
+ return;
248
+ const heartbeatPath = resolve(this.storagePath, `active-${this.sessionId}.pid`);
249
+ try {
250
+ if (!existsSync(this.storagePath)) {
251
+ mkdirSync(this.storagePath, { recursive: true, mode: 0o700 });
252
+ }
253
+ writeFileSync(heartbeatPath, String(pid), { mode: 0o600 });
254
+ }
255
+ catch (err) {
256
+ logger.warn('Failed to write heartbeat file', { error: String(err) });
257
+ }
258
+ }
259
+ /**
260
+ * Remove this MCP's heartbeat file. Called from the shutdown handler so the
261
+ * dashboard owner's next GC pass knows the buffer is up for adoption.
262
+ *
263
+ * No-op if no heartbeat was ever written or if the file is already gone.
264
+ */
265
+ removeHeartbeat() {
266
+ if (!this.sessionId)
267
+ return;
268
+ const heartbeatPath = resolve(this.storagePath, `active-${this.sessionId}.pid`);
269
+ try {
270
+ if (existsSync(heartbeatPath))
271
+ unlinkSync(heartbeatPath);
272
+ }
273
+ catch (err) {
274
+ logger.debug('Failed to remove heartbeat file', { error: String(err) });
275
+ }
276
+ }
277
+ /**
278
+ * Garbage-collect orphan per-session buffer files. A buffer is orphan when
279
+ * no live process is currently draining it; we determine that with two
280
+ * signals:
281
+ *
282
+ * 1. Heartbeat (authoritative): if `active-<sessionId>.pid` exists and
283
+ * its PID is alive, the buffer has an owner — leave it alone. If the
284
+ * heartbeat exists but the PID is dead (process crashed before
285
+ * cleanup), archive the buffer and delete the stale heartbeat.
286
+ * 2. Mtime fallback: if no heartbeat exists AND the buffer hasn't been
287
+ * written to in `ORPHAN_BUFFER_MTIME_MS`, treat as orphan. This catches
288
+ * crashes that happened before any heartbeat was written.
289
+ *
290
+ * Recently-modified buffers without a heartbeat are left alone — the MCP
291
+ * for that session may be in the breadcrumb-resolution window and not yet
292
+ * have written its heartbeat.
293
+ *
294
+ * The `activeSessionIds` set is also consulted: any session id present
295
+ * there is considered live regardless of heartbeat/mtime, which protects
296
+ * against heartbeat write delays on slow disks.
297
+ *
298
+ * Orphan buffers are renamed to `buffer-<sessionId>.jsonl.archived-<ts>`
299
+ * (preserved on disk for forensics, no longer scanned by the aggregator).
300
+ *
301
+ * @returns counts of files archived and stale heartbeats removed
302
+ */
303
+ gcOrphanBuffers(activeSessionIds) {
304
+ if (!existsSync(this.storagePath)) {
305
+ return { archived: 0, staleHeartbeats: 0 };
306
+ }
307
+ let entries;
308
+ try {
309
+ entries = readdirSync(this.storagePath);
310
+ }
311
+ catch (err) {
312
+ logger.warn('Failed to enumerate storage path for gcOrphanBuffers', { error: String(err) });
313
+ return { archived: 0, staleHeartbeats: 0 };
314
+ }
315
+ let archived = 0;
316
+ let staleHeartbeats = 0;
317
+ const now = Date.now();
318
+ for (const name of entries) {
319
+ // Match `buffer-<sessionId>.jsonl` exactly. Skip `.drain` companions and
320
+ // already-archived files so a follow-up pass doesn't re-process them.
321
+ if (!name.startsWith('buffer-') || !name.endsWith('.jsonl'))
322
+ continue;
323
+ if (name === 'buffer.jsonl')
324
+ continue;
325
+ const sessionId = name.slice('buffer-'.length, -'.jsonl'.length);
326
+ // Defense in depth — a malformed sessionId would mean a malformed
327
+ // heartbeat path; skip silently rather than archive.
328
+ if (!SESSION_ID_RE.test(sessionId))
329
+ continue;
330
+ const bufferPath = resolve(this.storagePath, name);
331
+ const heartbeatPath = resolve(this.storagePath, `active-${sessionId}.pid`);
332
+ const heartbeatExists = existsSync(heartbeatPath);
333
+ let ownerAlive = false;
334
+ let heartbeatStale = false;
335
+ if (heartbeatExists) {
336
+ try {
337
+ const raw = readFileSync(heartbeatPath, 'utf-8').trim();
338
+ const pid = Number.parseInt(raw, 10);
339
+ if (isPidAlive(pid)) {
340
+ ownerAlive = true;
341
+ }
342
+ else {
343
+ heartbeatStale = true;
344
+ }
345
+ }
346
+ catch {
347
+ // Unreadable heartbeat — treat as stale so we don't leak the file.
348
+ heartbeatStale = true;
349
+ }
350
+ }
351
+ // Cross-check against the caller-supplied set of session ids that are
352
+ // known to be live (via tool-call activity / LiveSessionRegistry). If
353
+ // present here, the buffer is owned even if no heartbeat is on disk yet.
354
+ if (activeSessionIds.has(sessionId))
355
+ ownerAlive = true;
356
+ if (ownerAlive)
357
+ continue;
358
+ // Mtime fallback only applies when no heartbeat is on disk: a stale
359
+ // heartbeat is enough on its own.
360
+ let archive = heartbeatStale;
361
+ if (!heartbeatExists) {
362
+ try {
363
+ const stat = statSync(bufferPath);
364
+ if (now - stat.mtimeMs > ORPHAN_BUFFER_MTIME_MS) {
365
+ archive = true;
366
+ }
367
+ }
368
+ catch {
369
+ // Buffer file vanished mid-scan — nothing to do.
370
+ continue;
371
+ }
372
+ }
373
+ if (!archive)
374
+ continue;
375
+ const archivedPath = `${bufferPath}.archived-${now}`;
376
+ try {
377
+ renameSync(bufferPath, archivedPath);
378
+ archived++;
379
+ logger.info('Archived orphan buffer', { sessionId, archivedPath });
380
+ // Only remove the heartbeat once the archive rename has succeeded.
381
+ // If the rename fails, leave the heartbeat so the next GC pass can
382
+ // retry rather than treating the buffer as heartbeat-free.
383
+ if (heartbeatExists) {
384
+ try {
385
+ unlinkSync(heartbeatPath);
386
+ if (heartbeatStale)
387
+ staleHeartbeats++;
388
+ }
389
+ catch (err) {
390
+ logger.debug('Failed to remove heartbeat after archive', {
391
+ sessionId,
392
+ error: String(err),
393
+ });
394
+ }
395
+ }
396
+ }
397
+ catch (err) {
398
+ logger.warn('Failed to archive orphan buffer', { sessionId, error: String(err) });
399
+ }
400
+ }
401
+ return { archived, staleHeartbeats };
402
+ }
403
+ /**
404
+ * Scan `<storage>/active-*.pid` heartbeat files and return the set of
405
+ * session ids whose owner PID is currently alive. Used by the dashboard
406
+ * owner to construct the `activeSessionIds` argument to
407
+ * `gcOrphanBuffers()`.
408
+ */
409
+ getActiveSessionIdsFromHeartbeats() {
410
+ const active = new Set();
411
+ if (!existsSync(this.storagePath))
412
+ return active;
413
+ let entries;
414
+ try {
415
+ entries = readdirSync(this.storagePath);
416
+ }
417
+ catch {
418
+ return active;
419
+ }
420
+ for (const name of entries) {
421
+ if (!name.startsWith('active-') || !name.endsWith('.pid'))
422
+ continue;
423
+ const sessionId = name.slice('active-'.length, -'.pid'.length);
424
+ if (!SESSION_ID_RE.test(sessionId))
425
+ continue;
426
+ try {
427
+ const raw = readFileSync(resolve(this.storagePath, name), 'utf-8').trim();
428
+ const pid = Number.parseInt(raw, 10);
429
+ if (isPidAlive(pid))
430
+ active.add(sessionId);
431
+ }
432
+ catch {
433
+ // Unreadable heartbeat — leave the session out of the live set so a
434
+ // mtime-based gc can still archive it eventually.
435
+ }
436
+ }
437
+ return active;
438
+ }
439
+ /**
440
+ * Garbage-collect stale per-PPID breadcrumb files at
441
+ * `<storage>/session-by-ppid/<pid>.txt`. A breadcrumb is stale when its
442
+ * PID names a process that no longer exists (Claude Code session ended).
443
+ *
444
+ * @returns the number of breadcrumb files deleted
445
+ */
446
+ gcStaleBreadcrumbs() {
447
+ const breadcrumbDir = resolve(this.storagePath, 'session-by-ppid');
448
+ if (!existsSync(breadcrumbDir))
449
+ return 0;
450
+ let entries;
451
+ try {
452
+ entries = readdirSync(breadcrumbDir);
453
+ }
454
+ catch (err) {
455
+ logger.warn('Failed to enumerate breadcrumb dir for gcStaleBreadcrumbs', {
456
+ error: String(err),
457
+ });
458
+ return 0;
459
+ }
460
+ let deleted = 0;
461
+ for (const name of entries) {
462
+ if (!name.endsWith('.txt'))
463
+ continue;
464
+ const pidStr = name.slice(0, -'.txt'.length);
465
+ const pid = Number.parseInt(pidStr, 10);
466
+ if (!Number.isFinite(pid) || pid <= 0)
467
+ continue;
468
+ if (isPidAlive(pid))
469
+ continue;
470
+ const path = resolve(breadcrumbDir, name);
471
+ try {
472
+ unlinkSync(path);
473
+ deleted++;
474
+ }
475
+ catch (err) {
476
+ logger.debug('Failed to delete stale breadcrumb', { pid, error: String(err) });
477
+ }
478
+ }
479
+ if (deleted > 0) {
480
+ logger.info('Cleaned up stale breadcrumbs', { deleted });
481
+ }
482
+ return deleted;
483
+ }
484
+ /**
485
+ * Migrate any pre-Fix-3 events left in the legacy shared `buffer.jsonl` into
486
+ * per-session `buffer-<sessionId>.jsonl` files, then delete the legacy file.
487
+ * Idempotent: returns 0 when the legacy file is absent or already empty.
488
+ *
489
+ * Concurrent-MCP-safe: the very first step is to atomically rename
490
+ * `buffer.jsonl` to `buffer.jsonl.migrating-<pid>`. The rename is the
491
+ * mutual-exclusion primitive — only one MCP wins; the loser sees ENOENT
492
+ * (the file is no longer at the legacy path) and skips migration.
493
+ *
494
+ * Failure recovery: if any partition append fails partway through, the
495
+ * `.migrating-<pid>` file is intentionally left in place. On the next boot
496
+ * the legacy file is gone, so we no-op; the partitioned files written so
497
+ * far hold the events that did make it across. The straggler events stuck
498
+ * in `.migrating-<pid>` are recovered manually (forensics) or dropped — we
499
+ * accept this trade because re-migrating from `.migrating-<pid>` would
500
+ * re-append already-migrated partitions and produce duplicate events,
501
+ * which is worse than a once-only loss bounded by the size of the legacy
502
+ * file at upgrade time.
503
+ *
504
+ * Returns the number of events migrated.
505
+ */
506
+ migrateLegacyBuffer() {
507
+ const legacyPath = resolve(this.storagePath, 'buffer.jsonl');
508
+ if (!existsSync(legacyPath))
509
+ return 0;
510
+ // Step 1: atomic rename for mutual exclusion against concurrent MCPs.
511
+ const migratingPath = resolve(this.storagePath, `buffer.jsonl.migrating-${process.pid}`);
512
+ // Guard against a prior crashed migration for this PID. On POSIX, rename(2)
513
+ // atomically replaces the destination (never EEXIST), so without this check
514
+ // a recycled PID would silently overwrite the stranded .migrating file and
515
+ // lose its events.
516
+ if (existsSync(migratingPath)) {
517
+ logger.info('Legacy buffer migration: prior crashed migration file found; skipping', {
518
+ migratingPath,
519
+ });
520
+ return 0;
521
+ }
522
+ try {
523
+ renameSync(legacyPath, migratingPath);
524
+ }
525
+ catch (err) {
526
+ const code = err.code;
527
+ if (code === 'ENOENT') {
528
+ // Another instance won the race.
529
+ logger.info('Legacy buffer migration: another instance is migrating; skipping');
530
+ return 0;
531
+ }
532
+ logger.warn('Failed to rename legacy buffer for migration', { error: String(err) });
533
+ return 0;
534
+ }
535
+ let raw;
536
+ try {
537
+ raw = readFileSync(migratingPath, 'utf-8');
538
+ }
539
+ catch (err) {
540
+ logger.warn('Failed to read legacy buffer.jsonl during migration', { error: String(err) });
541
+ // Leave the .migrating file in place for forensic recovery; we already
542
+ // own it via the rename so no concurrent process will retry.
543
+ return 0;
544
+ }
545
+ if (!raw.trim()) {
546
+ try {
547
+ unlinkSync(migratingPath);
548
+ }
549
+ catch {
550
+ // ignore
551
+ }
552
+ return 0;
553
+ }
554
+ const partitioned = new Map();
555
+ let count = 0;
556
+ for (const line of raw.split('\n')) {
557
+ if (!line.trim())
558
+ continue;
559
+ let parsed;
560
+ try {
561
+ parsed = JSON.parse(line);
562
+ }
563
+ catch {
564
+ continue;
565
+ }
566
+ if (parsed === null || typeof parsed !== 'object')
567
+ continue;
568
+ const sid = parsed.sessionId;
569
+ const key = typeof sid === 'string' && SESSION_ID_RE.test(sid) ? sid : 'unknown';
570
+ const bucket = partitioned.get(key) ?? [];
571
+ bucket.push(line);
572
+ partitioned.set(key, bucket);
573
+ count++;
574
+ }
575
+ for (const [sid, lines] of partitioned) {
576
+ const target = resolve(this.storagePath, `buffer-${sid}.jsonl`);
577
+ try {
578
+ appendFileSync(target, lines.join('\n') + '\n', { mode: 0o600 });
579
+ }
580
+ catch (err) {
581
+ // Mid-partition failure: the .migrating file stays on disk so the
582
+ // operator can recover forensically. We don't retry on next boot to
583
+ // avoid duplicate events in already-migrated partitions.
584
+ logger.warn('Failed to migrate legacy buffer entries; leaving .migrating file for recovery', { sessionId: sid, migratingPath, error: String(err) });
585
+ return count;
586
+ }
587
+ }
588
+ try {
589
+ unlinkSync(migratingPath);
590
+ }
591
+ catch (err) {
592
+ logger.warn('Migrated legacy buffer but failed to remove .migrating file', {
593
+ migratingPath,
594
+ error: String(err),
595
+ });
596
+ }
597
+ logger.info('Legacy buffer.jsonl migrated to per-session files', {
598
+ events: count,
599
+ sessions: partitioned.size,
600
+ });
601
+ return count;
602
+ }
603
+ drainPath(bufferPath) {
604
+ const tmpPath = bufferPath + '.drain';
605
+ // Recover from a previous failed drain — the .drain file has events that
606
+ // were never processed.
607
+ if (existsSync(tmpPath)) {
608
+ try {
609
+ if (existsSync(bufferPath)) {
610
+ const drainData = readFileSync(tmpPath, 'utf-8');
611
+ const bufferData = readFileSync(bufferPath, 'utf-8');
612
+ writeFileSync(bufferPath, drainData + (drainData.endsWith('\n') ? '' : '\n') + bufferData, { mode: 0o600 });
613
+ unlinkSync(tmpPath);
614
+ }
615
+ else {
616
+ renameSync(tmpPath, bufferPath);
617
+ }
618
+ }
619
+ catch {
620
+ logger.warn('Failed to recover .drain file — will retry next poll');
621
+ }
622
+ }
623
+ if (!existsSync(bufferPath)) {
624
+ return [];
625
+ }
626
+ try {
627
+ renameSync(bufferPath, tmpPath);
628
+ }
629
+ catch {
630
+ return [];
631
+ }
632
+ try {
633
+ const raw = readFileSync(tmpPath, 'utf-8');
634
+ unlinkSync(tmpPath);
635
+ if (!raw.trim()) {
636
+ return [];
637
+ }
638
+ const events = [];
639
+ for (const line of raw.split('\n')) {
640
+ if (!line.trim())
641
+ continue;
642
+ try {
643
+ events.push(JSON.parse(line));
644
+ }
645
+ catch {
646
+ logger.warn('Skipping malformed buffer line', { line: line.slice(0, 100) });
647
+ }
648
+ }
649
+ return events;
650
+ }
651
+ catch (err) {
652
+ logger.warn('Failed to drain buffer — will retry next poll', { error: String(err) });
653
+ return [];
654
+ }
655
+ }
656
+ /** The buffer path this store is currently scoped to (test helper). */
657
+ getBufferPath() {
658
+ return this.bufferPath;
659
+ }
660
+ /** The sessionId this store was scoped to, if any (test helper). */
661
+ getSessionId() {
662
+ return this.sessionId;
663
+ }
664
+ saveSession(session) {
665
+ if (!SESSION_ID_RE.test(session.sessionId)) {
666
+ logger.warn('Rejecting invalid sessionId for file path', { sessionId: session.sessionId });
667
+ return;
668
+ }
669
+ const sessionsDir = resolve(this.storagePath, 'sessions');
670
+ const filepath = resolve(sessionsDir, `${session.sessionId}.json`);
671
+ if (!filepath.startsWith(sessionsDir + sep)) {
672
+ throw new Error(`Session path escaped storage directory: ${filepath}`);
673
+ }
674
+ writeFileSync(filepath, JSON.stringify(session, null, 2) + '\n', { mode: 0o600 });
675
+ logger.debug('Session saved', { sessionId: session.sessionId });
676
+ }
677
+ loadRecentSessions(days) {
678
+ const sessionsDir = resolve(this.storagePath, 'sessions');
679
+ if (!existsSync(sessionsDir)) {
680
+ return [];
681
+ }
682
+ const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;
683
+ const sessions = [];
684
+ for (const file of readdirSync(sessionsDir)) {
685
+ if (!file.endsWith('.json'))
686
+ continue;
687
+ const filepath = join(sessionsDir, file);
688
+ try {
689
+ const stat = statSync(filepath);
690
+ if (stat.mtimeMs < cutoff)
691
+ continue;
692
+ const raw = readFileSync(filepath, 'utf-8');
693
+ const parsed = JSON.parse(raw);
694
+ // Guard against corrupted files (null, numbers, arrays) that would crash
695
+ // the downstream sort on .startTime.
696
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
697
+ sessions.push(parsed);
698
+ }
699
+ }
700
+ catch {
701
+ logger.warn('Skipping unreadable session file', { file });
702
+ }
703
+ }
704
+ return sessions.sort((a, b) => a.startTime - b.startTime);
705
+ }
706
+ appendAuditLog(entry) {
707
+ const date = new Date(entry.timestamp);
708
+ const dateStr = date.toISOString().slice(0, 10); // YYYY-MM-DD
709
+ const filename = `${dateStr}.jsonl`;
710
+ const filepath = resolve(this.storagePath, 'audit', filename);
711
+ try {
712
+ appendFileSync(filepath, JSON.stringify(entry) + '\n', { mode: 0o600 });
713
+ }
714
+ catch (err) {
715
+ logger.warn('Failed to append audit log', { error: String(err) });
716
+ }
717
+ }
718
+ }
719
+ //# sourceMappingURL=local-store.js.map