@elizaos/plugin-training 2.0.3-beta.5 → 2.0.3-beta.7

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 (363) hide show
  1. package/dist/backends/native.d.ts +96 -0
  2. package/dist/backends/native.d.ts.map +1 -0
  3. package/dist/backends/native.js +308 -0
  4. package/dist/backends/native.js.map +1 -0
  5. package/dist/cli/train.d.ts +22 -0
  6. package/dist/cli/train.d.ts.map +1 -0
  7. package/dist/cli/train.js +219 -0
  8. package/dist/cli/train.js.map +1 -0
  9. package/dist/core/action-benchmark-runner.d.ts +55 -0
  10. package/dist/core/action-benchmark-runner.d.ts.map +1 -0
  11. package/dist/core/action-benchmark-runner.js +341 -0
  12. package/dist/core/action-benchmark-runner.js.map +1 -0
  13. package/dist/core/artifact-store.d.ts +72 -0
  14. package/dist/core/artifact-store.d.ts.map +1 -0
  15. package/dist/core/artifact-store.js +50 -0
  16. package/dist/core/artifact-store.js.map +1 -0
  17. package/dist/core/benchmark-matrix-artifact.d.ts +102 -0
  18. package/dist/core/benchmark-matrix-artifact.d.ts.map +1 -0
  19. package/dist/core/benchmark-matrix-artifact.js +381 -0
  20. package/dist/core/benchmark-matrix-artifact.js.map +1 -0
  21. package/dist/core/benchmark-vs-cerebras-runner.d.ts +37 -0
  22. package/dist/core/benchmark-vs-cerebras-runner.d.ts.map +1 -0
  23. package/dist/core/benchmark-vs-cerebras-runner.js +151 -0
  24. package/dist/core/benchmark-vs-cerebras-runner.js.map +1 -0
  25. package/dist/core/cerebras-eval-model.d.ts +54 -0
  26. package/dist/core/cerebras-eval-model.d.ts.map +1 -0
  27. package/dist/core/cerebras-eval-model.js +249 -0
  28. package/dist/core/cerebras-eval-model.js.map +1 -0
  29. package/dist/core/cli.d.ts +15 -0
  30. package/dist/core/cli.d.ts.map +1 -0
  31. package/dist/core/cli.js +1003 -0
  32. package/dist/core/cli.js.map +1 -0
  33. package/dist/core/context-audit.d.ts +51 -0
  34. package/dist/core/context-audit.d.ts.map +1 -0
  35. package/dist/core/context-audit.js +166 -0
  36. package/dist/core/context-audit.js.map +1 -0
  37. package/dist/core/context-catalog.d.ts +47 -0
  38. package/dist/core/context-catalog.d.ts.map +1 -0
  39. package/dist/core/context-catalog.js +269 -0
  40. package/dist/core/context-catalog.js.map +1 -0
  41. package/dist/core/context-types.d.ts +3 -0
  42. package/dist/core/context-types.d.ts.map +1 -0
  43. package/dist/core/context-types.js +18 -0
  44. package/dist/core/context-types.js.map +1 -0
  45. package/dist/core/dataset-generator.d.ts +135 -0
  46. package/dist/core/dataset-generator.d.ts.map +1 -0
  47. package/dist/core/dataset-generator.js +895 -0
  48. package/dist/core/dataset-generator.js.map +1 -0
  49. package/dist/core/eliza1-benchmark-recipe.d.ts +18 -0
  50. package/dist/core/eliza1-benchmark-recipe.d.ts.map +1 -0
  51. package/dist/core/eliza1-benchmark-recipe.js +64 -0
  52. package/dist/core/eliza1-benchmark-recipe.js.map +1 -0
  53. package/dist/core/eliza1-bundle-stager.d.ts +57 -0
  54. package/dist/core/eliza1-bundle-stager.d.ts.map +1 -0
  55. package/dist/core/eliza1-bundle-stager.js +149 -0
  56. package/dist/core/eliza1-bundle-stager.js.map +1 -0
  57. package/dist/core/ensure-cron-job.d.ts +53 -0
  58. package/dist/core/ensure-cron-job.d.ts.map +1 -0
  59. package/dist/core/ensure-cron-job.js +51 -0
  60. package/dist/core/ensure-cron-job.js.map +1 -0
  61. package/dist/core/eval-comparison-artifact.d.ts +72 -0
  62. package/dist/core/eval-comparison-artifact.d.ts.map +1 -0
  63. package/dist/core/eval-comparison-artifact.js +281 -0
  64. package/dist/core/eval-comparison-artifact.js.map +1 -0
  65. package/dist/core/feed-generation-runner.d.ts +37 -0
  66. package/dist/core/feed-generation-runner.d.ts.map +1 -0
  67. package/dist/core/feed-generation-runner.js +232 -0
  68. package/dist/core/feed-generation-runner.js.map +1 -0
  69. package/dist/core/html-escape.d.ts +5 -0
  70. package/dist/core/html-escape.d.ts.map +1 -0
  71. package/dist/core/html-escape.js +11 -0
  72. package/dist/core/html-escape.js.map +1 -0
  73. package/dist/core/huggingface-dataset-ingest.d.ts +52 -0
  74. package/dist/core/huggingface-dataset-ingest.d.ts.map +1 -0
  75. package/dist/core/huggingface-dataset-ingest.js +134 -0
  76. package/dist/core/huggingface-dataset-ingest.js.map +1 -0
  77. package/dist/core/index.d.ts +29 -0
  78. package/dist/core/index.d.ts.map +1 -0
  79. package/dist/core/index.js +204 -0
  80. package/dist/core/index.js.map +1 -0
  81. package/dist/core/privacy-filter.d.ts +95 -0
  82. package/dist/core/privacy-filter.d.ts.map +1 -0
  83. package/dist/core/privacy-filter.js +324 -0
  84. package/dist/core/privacy-filter.js.map +1 -0
  85. package/dist/core/promotion-gate.d.ts +117 -0
  86. package/dist/core/promotion-gate.d.ts.map +1 -0
  87. package/dist/core/promotion-gate.js +85 -0
  88. package/dist/core/promotion-gate.js.map +1 -0
  89. package/dist/core/promotion-persist.d.ts +116 -0
  90. package/dist/core/promotion-persist.d.ts.map +1 -0
  91. package/dist/core/promotion-persist.js +93 -0
  92. package/dist/core/promotion-persist.js.map +1 -0
  93. package/dist/core/prompt-compare.d.ts +99 -0
  94. package/dist/core/prompt-compare.d.ts.map +1 -0
  95. package/dist/core/prompt-compare.js +210 -0
  96. package/dist/core/prompt-compare.js.map +1 -0
  97. package/dist/core/replay-validator.d.ts +136 -0
  98. package/dist/core/replay-validator.d.ts.map +1 -0
  99. package/dist/core/replay-validator.js +312 -0
  100. package/dist/core/replay-validator.js.map +1 -0
  101. package/dist/core/roleplay-executor.d.ts +123 -0
  102. package/dist/core/roleplay-executor.d.ts.map +1 -0
  103. package/dist/core/roleplay-executor.js +675 -0
  104. package/dist/core/roleplay-executor.js.map +1 -0
  105. package/dist/core/roleplay-trajectories.d.ts +54 -0
  106. package/dist/core/roleplay-trajectories.d.ts.map +1 -0
  107. package/dist/core/roleplay-trajectories.js +88 -0
  108. package/dist/core/roleplay-trajectories.js.map +1 -0
  109. package/dist/core/scenario-blueprints.d.ts +62 -0
  110. package/dist/core/scenario-blueprints.d.ts.map +1 -0
  111. package/dist/core/scenario-blueprints.js +850 -0
  112. package/dist/core/scenario-blueprints.js.map +1 -0
  113. package/dist/core/scenario-runner.d.ts +36 -0
  114. package/dist/core/scenario-runner.d.ts.map +1 -0
  115. package/dist/core/scenario-runner.js +216 -0
  116. package/dist/core/scenario-runner.js.map +1 -0
  117. package/dist/core/skill-scoring-cron.d.ts +57 -0
  118. package/dist/core/skill-scoring-cron.d.ts.map +1 -0
  119. package/dist/core/skill-scoring-cron.js +180 -0
  120. package/dist/core/skill-scoring-cron.js.map +1 -0
  121. package/dist/core/test-trajectory-collector.d.ts +37 -0
  122. package/dist/core/test-trajectory-collector.d.ts.map +1 -0
  123. package/dist/core/test-trajectory-collector.js +225 -0
  124. package/dist/core/test-trajectory-collector.js.map +1 -0
  125. package/dist/core/track-c-queue-task.d.ts +37 -0
  126. package/dist/core/track-c-queue-task.d.ts.map +1 -0
  127. package/dist/core/track-c-queue-task.js +104 -0
  128. package/dist/core/track-c-queue-task.js.map +1 -0
  129. package/dist/core/training-analysis-index.d.ts +104 -0
  130. package/dist/core/training-analysis-index.d.ts.map +1 -0
  131. package/dist/core/training-analysis-index.js +3297 -0
  132. package/dist/core/training-analysis-index.js.map +1 -0
  133. package/dist/core/training-collection-runner.d.ts +508 -0
  134. package/dist/core/training-collection-runner.d.ts.map +1 -0
  135. package/dist/core/training-collection-runner.js +2299 -0
  136. package/dist/core/training-collection-runner.js.map +1 -0
  137. package/dist/core/training-config.d.ts +52 -0
  138. package/dist/core/training-config.d.ts.map +1 -0
  139. package/dist/core/training-config.js +117 -0
  140. package/dist/core/training-config.js.map +1 -0
  141. package/dist/core/training-orchestrator.d.ts +112 -0
  142. package/dist/core/training-orchestrator.d.ts.map +1 -0
  143. package/dist/core/training-orchestrator.js +729 -0
  144. package/dist/core/training-orchestrator.js.map +1 -0
  145. package/dist/core/training-readiness-report.d.ts +52 -0
  146. package/dist/core/training-readiness-report.d.ts.map +1 -0
  147. package/dist/core/training-readiness-report.js +765 -0
  148. package/dist/core/training-readiness-report.js.map +1 -0
  149. package/dist/core/trajectory-consumer.d.ts +15 -0
  150. package/dist/core/trajectory-consumer.d.ts.map +1 -0
  151. package/dist/core/trajectory-consumer.js +61 -0
  152. package/dist/core/trajectory-consumer.js.map +1 -0
  153. package/dist/core/trajectory-export-bundle.d.ts +95 -0
  154. package/dist/core/trajectory-export-bundle.d.ts.map +1 -0
  155. package/dist/core/trajectory-export-bundle.js +561 -0
  156. package/dist/core/trajectory-export-bundle.js.map +1 -0
  157. package/dist/core/trajectory-export-cron.d.ts +57 -0
  158. package/dist/core/trajectory-export-cron.d.ts.map +1 -0
  159. package/dist/core/trajectory-export-cron.js +170 -0
  160. package/dist/core/trajectory-export-cron.js.map +1 -0
  161. package/dist/core/trajectory-hf-upload.d.ts +50 -0
  162. package/dist/core/trajectory-hf-upload.d.ts.map +1 -0
  163. package/dist/core/trajectory-hf-upload.js +111 -0
  164. package/dist/core/trajectory-hf-upload.js.map +1 -0
  165. package/dist/core/trajectory-task-datasets.d.ts +62 -0
  166. package/dist/core/trajectory-task-datasets.d.ts.map +1 -0
  167. package/dist/core/trajectory-task-datasets.js +427 -0
  168. package/dist/core/trajectory-task-datasets.js.map +1 -0
  169. package/dist/core/wait-for-service.d.ts +25 -0
  170. package/dist/core/wait-for-service.d.ts.map +1 -0
  171. package/dist/core/wait-for-service.js +19 -0
  172. package/dist/core/wait-for-service.js.map +1 -0
  173. package/dist/core/workspace-runtime.d.ts +4 -0
  174. package/dist/core/workspace-runtime.d.ts.map +1 -0
  175. package/dist/core/workspace-runtime.js +25 -0
  176. package/dist/core/workspace-runtime.js.map +1 -0
  177. package/dist/dspy/artifact.d.ts +54 -0
  178. package/dist/dspy/artifact.d.ts.map +1 -0
  179. package/dist/dspy/artifact.js +61 -0
  180. package/dist/dspy/artifact.js.map +1 -0
  181. package/dist/dspy/chain-of-thought.d.ts +27 -0
  182. package/dist/dspy/chain-of-thought.d.ts.map +1 -0
  183. package/dist/dspy/chain-of-thought.js +43 -0
  184. package/dist/dspy/chain-of-thought.js.map +1 -0
  185. package/dist/dspy/examples.d.ts +72 -0
  186. package/dist/dspy/examples.d.ts.map +1 -0
  187. package/dist/dspy/examples.js +105 -0
  188. package/dist/dspy/examples.js.map +1 -0
  189. package/dist/dspy/index.d.ts +15 -0
  190. package/dist/dspy/index.d.ts.map +1 -0
  191. package/dist/dspy/index.js +40 -0
  192. package/dist/dspy/index.js.map +1 -0
  193. package/dist/dspy/lm-adapter.d.ts +100 -0
  194. package/dist/dspy/lm-adapter.d.ts.map +1 -0
  195. package/dist/dspy/lm-adapter.js +81 -0
  196. package/dist/dspy/lm-adapter.js.map +1 -0
  197. package/dist/dspy/optimizers/dspy-bootstrap-fewshot.d.ts +23 -0
  198. package/dist/dspy/optimizers/dspy-bootstrap-fewshot.d.ts.map +1 -0
  199. package/dist/dspy/optimizers/dspy-bootstrap-fewshot.js +85 -0
  200. package/dist/dspy/optimizers/dspy-bootstrap-fewshot.js.map +1 -0
  201. package/dist/dspy/optimizers/dspy-copro.d.ts +29 -0
  202. package/dist/dspy/optimizers/dspy-copro.d.ts.map +1 -0
  203. package/dist/dspy/optimizers/dspy-copro.js +141 -0
  204. package/dist/dspy/optimizers/dspy-copro.js.map +1 -0
  205. package/dist/dspy/optimizers/dspy-mipro.d.ts +37 -0
  206. package/dist/dspy/optimizers/dspy-mipro.d.ts.map +1 -0
  207. package/dist/dspy/optimizers/dspy-mipro.js +194 -0
  208. package/dist/dspy/optimizers/dspy-mipro.js.map +1 -0
  209. package/dist/dspy/optimizers/index.d.ts +5 -0
  210. package/dist/dspy/optimizers/index.d.ts.map +1 -0
  211. package/dist/dspy/optimizers/index.js +11 -0
  212. package/dist/dspy/optimizers/index.js.map +1 -0
  213. package/dist/dspy/optimizers/types.d.ts +39 -0
  214. package/dist/dspy/optimizers/types.d.ts.map +1 -0
  215. package/dist/dspy/optimizers/types.js +1 -0
  216. package/dist/dspy/optimizers/types.js.map +1 -0
  217. package/dist/dspy/predict.d.ts +49 -0
  218. package/dist/dspy/predict.d.ts.map +1 -0
  219. package/dist/dspy/predict.js +73 -0
  220. package/dist/dspy/predict.js.map +1 -0
  221. package/dist/dspy/signature.d.ts +88 -0
  222. package/dist/dspy/signature.d.ts.map +1 -0
  223. package/dist/dspy/signature.js +205 -0
  224. package/dist/dspy/signature.js.map +1 -0
  225. package/dist/index.d.ts +15 -0
  226. package/dist/index.d.ts.map +1 -0
  227. package/dist/index.js +15 -0
  228. package/dist/index.js.map +1 -0
  229. package/dist/optimizers/bootstrap-fewshot.d.ts +42 -0
  230. package/dist/optimizers/bootstrap-fewshot.d.ts.map +1 -0
  231. package/dist/optimizers/bootstrap-fewshot.js +92 -0
  232. package/dist/optimizers/bootstrap-fewshot.js.map +1 -0
  233. package/dist/optimizers/gepa.d.ts +63 -0
  234. package/dist/optimizers/gepa.d.ts.map +1 -0
  235. package/dist/optimizers/gepa.js +232 -0
  236. package/dist/optimizers/gepa.js.map +1 -0
  237. package/dist/optimizers/index.d.ts +7 -0
  238. package/dist/optimizers/index.d.ts.map +1 -0
  239. package/dist/optimizers/index.js +51 -0
  240. package/dist/optimizers/index.js.map +1 -0
  241. package/dist/optimizers/instruction-search.d.ts +39 -0
  242. package/dist/optimizers/instruction-search.d.ts.map +1 -0
  243. package/dist/optimizers/instruction-search.js +108 -0
  244. package/dist/optimizers/instruction-search.js.map +1 -0
  245. package/dist/optimizers/prompt-evolution.d.ts +39 -0
  246. package/dist/optimizers/prompt-evolution.d.ts.map +1 -0
  247. package/dist/optimizers/prompt-evolution.js +101 -0
  248. package/dist/optimizers/prompt-evolution.js.map +1 -0
  249. package/dist/optimizers/scoring.d.ts +139 -0
  250. package/dist/optimizers/scoring.d.ts.map +1 -0
  251. package/dist/optimizers/scoring.js +299 -0
  252. package/dist/optimizers/scoring.js.map +1 -0
  253. package/dist/optimizers/types.d.ts +105 -0
  254. package/dist/optimizers/types.d.ts.map +1 -0
  255. package/dist/optimizers/types.js +1 -0
  256. package/dist/optimizers/types.js.map +1 -0
  257. package/dist/register-runtime.d.ts +3 -0
  258. package/dist/register-runtime.d.ts.map +1 -0
  259. package/dist/register-runtime.js +60 -0
  260. package/dist/register-runtime.js.map +1 -0
  261. package/dist/register-terminal-view.d.ts +15 -0
  262. package/dist/register-terminal-view.d.ts.map +1 -0
  263. package/dist/register-terminal-view.js +31 -0
  264. package/dist/register-terminal-view.js.map +1 -0
  265. package/dist/routes/experience-routes.d.ts +21 -0
  266. package/dist/routes/experience-routes.d.ts.map +1 -0
  267. package/dist/routes/experience-routes.js +513 -0
  268. package/dist/routes/experience-routes.js.map +1 -0
  269. package/dist/routes/index.d.ts +5 -0
  270. package/dist/routes/index.d.ts.map +1 -0
  271. package/dist/routes/index.js +17 -0
  272. package/dist/routes/index.js.map +1 -0
  273. package/dist/routes/training-routes.d.ts +10 -0
  274. package/dist/routes/training-routes.d.ts.map +1 -0
  275. package/dist/routes/training-routes.js +1239 -0
  276. package/dist/routes/training-routes.js.map +1 -0
  277. package/dist/routes/training-vast-routes.d.ts +35 -0
  278. package/dist/routes/training-vast-routes.d.ts.map +1 -0
  279. package/dist/routes/training-vast-routes.js +249 -0
  280. package/dist/routes/training-vast-routes.js.map +1 -0
  281. package/dist/routes/trajectory-routes.d.ts +19 -0
  282. package/dist/routes/trajectory-routes.d.ts.map +1 -0
  283. package/dist/routes/trajectory-routes.js +1122 -0
  284. package/dist/routes/trajectory-routes.js.map +1 -0
  285. package/dist/services/index.d.ts +9 -0
  286. package/dist/services/index.d.ts.map +1 -0
  287. package/dist/services/index.js +63 -0
  288. package/dist/services/index.js.map +1 -0
  289. package/dist/services/training-backend-check.d.ts +8 -0
  290. package/dist/services/training-backend-check.d.ts.map +1 -0
  291. package/dist/services/training-backend-check.js +31 -0
  292. package/dist/services/training-backend-check.js.map +1 -0
  293. package/dist/services/training-service-like.d.ts +40 -0
  294. package/dist/services/training-service-like.d.ts.map +1 -0
  295. package/dist/services/training-service-like.js +1 -0
  296. package/dist/services/training-service-like.js.map +1 -0
  297. package/dist/services/training-service-registry.d.ts +4 -0
  298. package/dist/services/training-service-registry.d.ts.map +1 -0
  299. package/dist/services/training-service-registry.js +12 -0
  300. package/dist/services/training-service-registry.js.map +1 -0
  301. package/dist/services/training-service.d.ts +59 -0
  302. package/dist/services/training-service.d.ts.map +1 -0
  303. package/dist/services/training-service.js +154 -0
  304. package/dist/services/training-service.js.map +1 -0
  305. package/dist/services/training-trigger.d.ts +177 -0
  306. package/dist/services/training-trigger.d.ts.map +1 -0
  307. package/dist/services/training-trigger.js +300 -0
  308. package/dist/services/training-trigger.js.map +1 -0
  309. package/dist/services/training-vast-service.d.ts +149 -0
  310. package/dist/services/training-vast-service.d.ts.map +1 -0
  311. package/dist/services/training-vast-service.js +648 -0
  312. package/dist/services/training-vast-service.js.map +1 -0
  313. package/dist/services/vast-inference-stats.d.ts +37 -0
  314. package/dist/services/vast-inference-stats.d.ts.map +1 -0
  315. package/dist/services/vast-inference-stats.js +81 -0
  316. package/dist/services/vast-inference-stats.js.map +1 -0
  317. package/dist/services/vast-job-store.d.ts +74 -0
  318. package/dist/services/vast-job-store.d.ts.map +1 -0
  319. package/dist/services/vast-job-store.js +194 -0
  320. package/dist/services/vast-job-store.js.map +1 -0
  321. package/dist/services/vast-subprocess.d.ts +27 -0
  322. package/dist/services/vast-subprocess.d.ts.map +1 -0
  323. package/dist/services/vast-subprocess.js +78 -0
  324. package/dist/services/vast-subprocess.js.map +1 -0
  325. package/dist/setup-routes.d.ts +17 -0
  326. package/dist/setup-routes.d.ts.map +1 -0
  327. package/dist/setup-routes.js +319 -0
  328. package/dist/setup-routes.js.map +1 -0
  329. package/dist/ui/FineTuningSpatialView.d.ts +49 -0
  330. package/dist/ui/FineTuningSpatialView.d.ts.map +1 -0
  331. package/dist/ui/FineTuningSpatialView.js +154 -0
  332. package/dist/ui/FineTuningSpatialView.js.map +1 -0
  333. package/dist/ui/FineTuningView.d.ts +7 -0
  334. package/dist/ui/FineTuningView.d.ts.map +1 -0
  335. package/dist/ui/FineTuningView.helpers.d.ts +17 -0
  336. package/dist/ui/FineTuningView.helpers.d.ts.map +1 -0
  337. package/dist/ui/FineTuningView.helpers.js +30 -0
  338. package/dist/ui/FineTuningView.helpers.js.map +1 -0
  339. package/dist/ui/FineTuningView.interact.d.ts +2 -0
  340. package/dist/ui/FineTuningView.interact.d.ts.map +1 -0
  341. package/dist/ui/FineTuningView.interact.js +300 -0
  342. package/dist/ui/FineTuningView.interact.js.map +1 -0
  343. package/dist/ui/FineTuningView.js +4653 -0
  344. package/dist/ui/FineTuningView.js.map +1 -0
  345. package/dist/ui/fine-tuning-panels.d.ts +100 -0
  346. package/dist/ui/fine-tuning-panels.d.ts.map +1 -0
  347. package/dist/ui/fine-tuning-panels.helpers.d.ts +19 -0
  348. package/dist/ui/fine-tuning-panels.helpers.d.ts.map +1 -0
  349. package/dist/ui/fine-tuning-panels.helpers.js +77 -0
  350. package/dist/ui/fine-tuning-panels.helpers.js.map +1 -0
  351. package/dist/ui/fine-tuning-panels.js +928 -0
  352. package/dist/ui/fine-tuning-panels.js.map +1 -0
  353. package/dist/ui/index.d.ts +5 -0
  354. package/dist/ui/index.d.ts.map +1 -0
  355. package/dist/ui/index.js +5 -0
  356. package/dist/ui/index.js.map +1 -0
  357. package/dist/ui/training-view-bundle.d.ts +3 -0
  358. package/dist/ui/training-view-bundle.d.ts.map +1 -0
  359. package/dist/ui/training-view-bundle.js +7 -0
  360. package/dist/ui/training-view-bundle.js.map +1 -0
  361. package/dist/views/bundle.js +5312 -0
  362. package/dist/views/bundle.js.map +1 -0
  363. package/package.json +7 -7
@@ -0,0 +1,324 @@
1
+ import { createHash } from "node:crypto";
2
+ const DEFAULT_PLATFORMS = [
3
+ "telegram",
4
+ "discord",
5
+ "slack",
6
+ "matrix",
7
+ "signal",
8
+ "whatsapp",
9
+ "twitter",
10
+ "instagram",
11
+ "email"
12
+ ];
13
+ const HANDLE_PATTERN = /(@[a-zA-Z0-9_.-]{2,})/g;
14
+ const DEFAULT_CREDENTIAL_PATTERNS = [
15
+ // `sk-ant-…` must be matched before the generic `sk-…` so the more specific
16
+ // Anthropic label wins.
17
+ { label: "anthropic-key", pattern: /\bsk-ant-[A-Za-z0-9_-]{16,}\b/g },
18
+ { label: "openai-key", pattern: /\bsk-[A-Za-z0-9_-]{16,}\b/g },
19
+ {
20
+ label: "bearer",
21
+ pattern: /\bBearer\s+[A-Za-z0-9._-]{16,}\b/g
22
+ },
23
+ {
24
+ label: "github-token",
25
+ pattern: /\bghp_[A-Za-z0-9]{20,}\b/g
26
+ },
27
+ {
28
+ label: "aws-access-key",
29
+ pattern: /\bAKIA[0-9A-Z]{16}\b/g
30
+ }
31
+ ];
32
+ const EMAIL_REPLACEMENT = "[REDACTED_EMAIL]";
33
+ const PHONE_REPLACEMENT = "[REDACTED_PHONE]";
34
+ const ADDRESS_REPLACEMENT = "[REDACTED_ADDRESS]";
35
+ const EMAIL_PATTERN = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
36
+ const STREET_SUFFIXES = "St|Street|Ave|Avenue|Blvd|Boulevard|Rd|Road|Ln|Lane|Dr|Drive|Ct|Court|Pl|Place|Way|Pkwy|Parkway|Ter|Terrace|Cir|Circle|Hwy|Highway|Sq|Square|Trl|Trail|Loop";
37
+ const UNIT_DESIGNATORS = "Apt|Apartment|Suite|Ste|Unit|Bldg|Building|Fl|Floor|Rm|Room|#";
38
+ const US_STATES = "AL|AK|AZ|AR|CA|CO|CT|DE|FL|GA|HI|ID|IL|IN|IA|KS|KY|LA|ME|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|OH|OK|OR|PA|RI|SC|SD|TN|TX|UT|VT|VA|WA|WV|WI|WY|DC";
39
+ const DEFAULT_ADDRESS_PATTERNS = [
40
+ // 1. Numbered street + suffix + optional unit, optionally followed by a
41
+ // city, state, ZIP tail: `1600 Amphitheatre Parkway, Suite 200,
42
+ // Mountain View, CA 94043`.
43
+ new RegExp(
44
+ String.raw`\b\d{1,6}\s+(?:[A-Za-z0-9.'-]+\s+){0,4}(?:${STREET_SUFFIXES})\b` + String.raw`(?:\s*,?\s*(?:${UNIT_DESIGNATORS})\.?\s*[A-Za-z0-9-]+)?` + String.raw`(?:\s*,\s*[A-Za-z .'-]+,?\s*(?:${US_STATES})\s+\d{5}(?:-\d{4})?)?`,
45
+ "gi"
46
+ ),
47
+ // 2. `PO Box 4242` / `P.O. Box 4242`.
48
+ /\bP\.?\s?O\.?\s?Box\s+\d{1,7}\b/gi,
49
+ // 3. Standalone city, state, ZIP tail: `Mountain View, CA 94043`.
50
+ new RegExp(
51
+ String.raw`\b[A-Za-z .'-]+,\s*(?:${US_STATES})\s+\d{5}(?:-\d{4})?\b`,
52
+ "g"
53
+ )
54
+ ];
55
+ const DEFAULT_PHONE_PATTERNS = [
56
+ // 1. E.164 / international with leading `+`: `+44 20 7946 0958`,
57
+ // `+1-415-555-0123`, `+442079460958`.
58
+ /\+\d{1,3}(?:[\s.-]?\d{1,4}){1,5}\b/g,
59
+ // 2. NANP with explicit separators (a separator is REQUIRED between groups
60
+ // so bare 10-digit runs survive): `(415) 555-0123`, `415-555-0123`,
61
+ // `415.555.0123`, `415 555 0123`. No leading `\b` before `(` — there is
62
+ // no word boundary between a space and `(`.
63
+ /(?:\(\d{3}\)[\s.-]?|\b\d{3}[\s.-])\d{3}[\s.-]\d{4}\b/g
64
+ ];
65
+ const GEO_REPLACEMENT = "[REDACTED_GEO]";
66
+ const DEFAULT_GEO_PATTERNS = [
67
+ // 1. JSON `"coords":{"latitude":..,"longitude":..[,...]}` (Capacitor shape).
68
+ /"coords"\s*:\s*\{\s*"latitude"\s*:\s*-?\d+(?:\.\d+)?\s*,\s*"longitude"\s*:\s*-?\d+(?:\.\d+)?(?:\s*,\s*"[A-Za-z_][A-Za-z0-9_]*"\s*:\s*[^,}]+)*\s*\}/g,
69
+ // 2. Bare JSON pair `"latitude":..,"longitude":..`.
70
+ /"latitude"\s*:\s*-?\d+(?:\.\d+)?\s*,\s*"longitude"\s*:\s*-?\d+(?:\.\d+)?/g,
71
+ // 3. `current location: 37.7, -122.4` / `coords: ...` / `coordinates=...`.
72
+ /\b(?:current\s+location|location|coords|coordinates)\s*[:=]\s*-?\d+(?:\.\d+)?\s*,\s*-?\d+(?:\.\d+)?/gi,
73
+ // 4. Labeled `lat: .., lng: ..` / `latitude=.., longitude=..`.
74
+ /\b(?:lat|latitude)\s*[:=]\s*-?\d+(?:\.\d+)?\s*[,;]\s*(?:lng|lon|long|longitude)\s*[:=]\s*-?\d+(?:\.\d+)?/gi,
75
+ // 5. Bare decimal pair `37.7749, -122.4194` (both numbers must have a
76
+ // fractional component to avoid matching integer pairs).
77
+ /\b-?\d{1,3}\.\d{2,}\s*,\s*-?\d{1,3}\.\d{2,}\b/g
78
+ ];
79
+ function snapshotEnvCredentials(envKeys) {
80
+ const interesting = /KEY|TOKEN|SECRET|PASSWORD|API|CREDENTIAL/i;
81
+ const out = [];
82
+ for (const key of envKeys) {
83
+ if (!interesting.test(key)) continue;
84
+ const value = process.env[key];
85
+ if (typeof value !== "string") continue;
86
+ if (value.length < 8) continue;
87
+ out.push(value);
88
+ }
89
+ return out;
90
+ }
91
+ function redactCredentials(value, patterns, credentialValues, state) {
92
+ let out = value;
93
+ for (const { label, pattern } of patterns) {
94
+ out = out.replace(pattern, () => {
95
+ state.redactionCount += 1;
96
+ return `<REDACTED:${label}>`;
97
+ });
98
+ }
99
+ for (const credValue of credentialValues) {
100
+ if (!credValue) continue;
101
+ const escaped = credValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
102
+ const re = new RegExp(escaped, "g");
103
+ out = out.replace(re, () => {
104
+ state.redactionCount += 1;
105
+ return "<REDACTED:env-secret>";
106
+ });
107
+ }
108
+ return out;
109
+ }
110
+ function redactGeo(value, state) {
111
+ let out = value;
112
+ for (const pattern of DEFAULT_GEO_PATTERNS) {
113
+ out = out.replace(pattern, () => {
114
+ state.redactionCount += 1;
115
+ return GEO_REPLACEMENT;
116
+ });
117
+ }
118
+ return out;
119
+ }
120
+ function redactPii(value, state) {
121
+ let out = value;
122
+ out = out.replace(EMAIL_PATTERN, () => {
123
+ state.redactionCount += 1;
124
+ return EMAIL_REPLACEMENT;
125
+ });
126
+ for (const pattern of DEFAULT_ADDRESS_PATTERNS) {
127
+ out = out.replace(pattern, () => {
128
+ state.redactionCount += 1;
129
+ return ADDRESS_REPLACEMENT;
130
+ });
131
+ }
132
+ for (const pattern of DEFAULT_PHONE_PATTERNS) {
133
+ out = out.replace(pattern, () => {
134
+ state.redactionCount += 1;
135
+ return PHONE_REPLACEMENT;
136
+ });
137
+ }
138
+ return out;
139
+ }
140
+ function anonymizeHandles(value, options, state) {
141
+ const platforms = options.platforms ?? DEFAULT_PLATFORMS;
142
+ const entityHits = /* @__PURE__ */ new Set();
143
+ if (!options.anonymizer) {
144
+ return { result: value, entityHits };
145
+ }
146
+ const result = value.replace(HANDLE_PATTERN, (match, handle) => {
147
+ const stripped = handle.startsWith("@") ? handle.slice(1) : handle;
148
+ for (const platform of platforms) {
149
+ const entityId = options.anonymizer?.resolveEntityId(platform, stripped);
150
+ if (entityId) {
151
+ state.anonymizationCount += 1;
152
+ entityHits.add(entityId);
153
+ return `<entity:${entityId}>`;
154
+ }
155
+ }
156
+ return match;
157
+ });
158
+ return { result, entityHits };
159
+ }
160
+ function transformText(value, options, credentialValues, credentialPatterns, state, collectedEntities) {
161
+ const geoRedacted = redactGeo(value, state);
162
+ const credRedacted = redactCredentials(
163
+ geoRedacted,
164
+ credentialPatterns,
165
+ credentialValues,
166
+ state
167
+ );
168
+ const piiRedacted = redactPii(credRedacted, state);
169
+ const { result, entityHits } = anonymizeHandles(piiRedacted, options, state);
170
+ for (const entityId of entityHits) collectedEntities.add(entityId);
171
+ return result;
172
+ }
173
+ function transformDeep(value, options, credentialValues, credentialPatterns, state, collectedEntities) {
174
+ if (typeof value === "string") {
175
+ return transformText(
176
+ value,
177
+ options,
178
+ credentialValues,
179
+ credentialPatterns,
180
+ state,
181
+ collectedEntities
182
+ );
183
+ }
184
+ if (Array.isArray(value)) {
185
+ return value.map(
186
+ (entry) => transformDeep(
187
+ entry,
188
+ options,
189
+ credentialValues,
190
+ credentialPatterns,
191
+ state,
192
+ collectedEntities
193
+ )
194
+ );
195
+ }
196
+ if (value && typeof value === "object") {
197
+ const out = {};
198
+ for (const [key, entry] of Object.entries(
199
+ value
200
+ )) {
201
+ out[key] = transformDeep(
202
+ entry,
203
+ options,
204
+ credentialValues,
205
+ credentialPatterns,
206
+ state,
207
+ collectedEntities
208
+ );
209
+ }
210
+ return out;
211
+ }
212
+ return value;
213
+ }
214
+ function applyPrivacyFilter(trajectories, options = {}) {
215
+ const credentialPatterns = [
216
+ ...DEFAULT_CREDENTIAL_PATTERNS,
217
+ ...options.extraCredentialPatterns ?? []
218
+ ];
219
+ const envKeys = options.envKeySnapshot ?? Object.keys(process.env);
220
+ const credentialValues = snapshotEnvCredentials(envKeys);
221
+ const dropped = [];
222
+ const filtered = [];
223
+ const state = {
224
+ anonymizationCount: 0,
225
+ redactionCount: 0
226
+ };
227
+ for (const trajectory of trajectories) {
228
+ const trajectoryEntities = /* @__PURE__ */ new Set();
229
+ const cloned = JSON.parse(JSON.stringify(trajectory));
230
+ const steps = cloned.steps ?? [];
231
+ for (const step of steps) {
232
+ for (const call of step.llmCalls ?? []) {
233
+ if (typeof call.systemPrompt === "string") {
234
+ call.systemPrompt = transformText(
235
+ call.systemPrompt,
236
+ options,
237
+ credentialValues,
238
+ credentialPatterns,
239
+ state,
240
+ trajectoryEntities
241
+ );
242
+ }
243
+ if (typeof call.userPrompt === "string") {
244
+ call.userPrompt = transformText(
245
+ call.userPrompt,
246
+ options,
247
+ credentialValues,
248
+ credentialPatterns,
249
+ state,
250
+ trajectoryEntities
251
+ );
252
+ }
253
+ if (typeof call.response === "string") {
254
+ call.response = transformText(
255
+ call.response,
256
+ options,
257
+ credentialValues,
258
+ credentialPatterns,
259
+ state,
260
+ trajectoryEntities
261
+ );
262
+ }
263
+ }
264
+ for (const access of step.providerAccesses ?? []) {
265
+ if (access.data !== void 0) {
266
+ access.data = transformDeep(
267
+ access.data,
268
+ options,
269
+ credentialValues,
270
+ credentialPatterns,
271
+ state,
272
+ trajectoryEntities
273
+ );
274
+ }
275
+ }
276
+ }
277
+ if (cloned.metadata && typeof cloned.metadata === "object") {
278
+ cloned.metadata = transformDeep(
279
+ cloned.metadata,
280
+ options,
281
+ credentialValues,
282
+ credentialPatterns,
283
+ state,
284
+ trajectoryEntities
285
+ );
286
+ }
287
+ const lookup = options.anonymizer?.getPrivacyLevel;
288
+ if (lookup) {
289
+ let isPrivate = false;
290
+ for (const entityId of trajectoryEntities) {
291
+ if (lookup(entityId) === "private") {
292
+ isPrivate = true;
293
+ break;
294
+ }
295
+ }
296
+ if (isPrivate) {
297
+ dropped.push({
298
+ trajectoryId: trajectory.trajectoryId,
299
+ reason: "entity-private"
300
+ });
301
+ continue;
302
+ }
303
+ }
304
+ filtered.push(cloned);
305
+ }
306
+ return {
307
+ trajectories: filtered,
308
+ dropped,
309
+ redactionCount: state.redactionCount,
310
+ anonymizationCount: state.anonymizationCount
311
+ };
312
+ }
313
+ function createHashAnonymizer(salt = "") {
314
+ return {
315
+ resolveEntityId(platform, handle) {
316
+ return createHash("sha256").update(`${salt}:${platform.toLowerCase()}:${handle.toLowerCase()}`).digest("hex").slice(0, 16);
317
+ }
318
+ };
319
+ }
320
+ export {
321
+ applyPrivacyFilter,
322
+ createHashAnonymizer
323
+ };
324
+ //# sourceMappingURL=privacy-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/privacy-filter.ts"],"sourcesContent":["/**\n * Privacy filter for trajectory exports.\n *\n * Jobs:\n * 1. Anonymize cross-platform handles by mapping them to opaque entity IDs\n * (the caller supplies a lookup callback so app-training does not have\n * to depend on the relationships service directly). `createHashAnonymizer`\n * provides a stable, dependency-free default.\n * 2. Honor `ContactPreferences.privacyLevel` — drop entire trajectories if\n * the participating entity is `private`.\n * 3. Strip credential references — env-var name patterns from process.env,\n * plus the usual API key shapes (`sk-ant-…`, `sk-…`, `Bearer …`).\n * 4. Strip geo coordinates — bare decimal pairs, labeled `lat:`/`lng:`\n * values, and JSON `\"coords\":{\"latitude\":..,\"longitude\":..}` blocks\n * from the Location plugin — replaced with `[REDACTED_GEO]`.\n * 5. Strip PII — email addresses (`[REDACTED_EMAIL]`), phone numbers\n * (`[REDACTED_PHONE]`), and street/PO-box/city-state-ZIP addresses\n * (`[REDACTED_ADDRESS]`).\n *\n * Walks every string in `steps[].llmCalls[].{systemPrompt,userPrompt,response}`,\n * `steps[].providerAccesses[].data`, and the top-level `metadata` object.\n *\n * Run automatically before any export to disk; required for any cloud upload.\n */\n\nimport { createHash } from \"node:crypto\";\n\nexport type PrivacyLevel = \"public\" | \"limited\" | \"private\";\n\nexport interface AnonymizerLookup {\n /** Look up the opaque entity ID for a (platform, handle) pair. */\n resolveEntityId(platform: string, handle: string): string | null;\n /** Look up the privacy level for an entity. Defaults to \"public\". */\n getPrivacyLevel?(entityId: string): PrivacyLevel | undefined;\n}\n\nexport interface PrivacyFilterOptions {\n /** Optional anonymizer lookup. If absent, handles pass through unchanged. */\n anonymizer?: AnonymizerLookup;\n /**\n * Additional credential shapes to redact. Each entry is matched as a\n * RegExp against any string field; matches are replaced with\n * `<REDACTED:{label}>`.\n */\n extraCredentialPatterns?: Array<{ label: string; pattern: RegExp }>;\n /**\n * Snapshot of `process.env` keys to treat as credential names.\n * Defaults to capturing all env names matching the standard secret regex.\n */\n envKeySnapshot?: string[];\n /**\n * Hard list of platforms the anonymizer recognizes. Used to constrain\n * cross-platform handle detection. Defaults to common platforms.\n */\n platforms?: string[];\n}\n\nexport interface FilterableTrajectory {\n trajectoryId?: string;\n steps?: Array<{\n llmCalls?: Array<{\n systemPrompt?: string;\n userPrompt?: string;\n response?: string;\n }>;\n providerAccesses?: Array<{\n data?: unknown;\n }>;\n }>;\n metadata?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface FilterResult<T> {\n trajectories: T[];\n dropped: Array<{ trajectoryId?: string; reason: string }>;\n redactionCount: number;\n anonymizationCount: number;\n}\n\nconst DEFAULT_PLATFORMS = [\n \"telegram\",\n \"discord\",\n \"slack\",\n \"matrix\",\n \"signal\",\n \"whatsapp\",\n \"twitter\",\n \"instagram\",\n \"email\",\n];\n\nconst HANDLE_PATTERN = /(@[a-zA-Z0-9_.-]{2,})/g;\n\nconst DEFAULT_CREDENTIAL_PATTERNS: Array<{ label: string; pattern: RegExp }> = [\n // `sk-ant-…` must be matched before the generic `sk-…` so the more specific\n // Anthropic label wins.\n { label: \"anthropic-key\", pattern: /\\bsk-ant-[A-Za-z0-9_-]{16,}\\b/g },\n { label: \"openai-key\", pattern: /\\bsk-[A-Za-z0-9_-]{16,}\\b/g },\n {\n label: \"bearer\",\n pattern: /\\bBearer\\s+[A-Za-z0-9._-]{16,}\\b/g,\n },\n {\n label: \"github-token\",\n pattern: /\\bghp_[A-Za-z0-9]{20,}\\b/g,\n },\n {\n label: \"aws-access-key\",\n pattern: /\\bAKIA[0-9A-Z]{16}\\b/g,\n },\n];\n\n/**\n * PII redaction (email / phone / address). Applied in the order\n * email → address → phone so phone-like number runs inside an address tail\n * (e.g. ZIP codes) are consumed by the address pass first, and bare digit\n * runs without separators survive.\n */\nconst EMAIL_REPLACEMENT = \"[REDACTED_EMAIL]\";\nconst PHONE_REPLACEMENT = \"[REDACTED_PHONE]\";\nconst ADDRESS_REPLACEMENT = \"[REDACTED_ADDRESS]\";\n\nconst EMAIL_PATTERN = /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b/g;\n\nconst STREET_SUFFIXES =\n \"St|Street|Ave|Avenue|Blvd|Boulevard|Rd|Road|Ln|Lane|Dr|Drive|Ct|Court|Pl|Place|Way|Pkwy|Parkway|Ter|Terrace|Cir|Circle|Hwy|Highway|Sq|Square|Trl|Trail|Loop\";\nconst UNIT_DESIGNATORS =\n \"Apt|Apartment|Suite|Ste|Unit|Bldg|Building|Fl|Floor|Rm|Room|#\";\nconst US_STATES =\n \"AL|AK|AZ|AR|CA|CO|CT|DE|FL|GA|HI|ID|IL|IN|IA|KS|KY|LA|ME|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|OH|OK|OR|PA|RI|SC|SD|TN|TX|UT|VT|VA|WA|WV|WI|WY|DC\";\n\nconst DEFAULT_ADDRESS_PATTERNS: RegExp[] = [\n // 1. Numbered street + suffix + optional unit, optionally followed by a\n // city, state, ZIP tail: `1600 Amphitheatre Parkway, Suite 200,\n // Mountain View, CA 94043`.\n new RegExp(\n String.raw`\\b\\d{1,6}\\s+(?:[A-Za-z0-9.'-]+\\s+){0,4}(?:${STREET_SUFFIXES})\\b` +\n String.raw`(?:\\s*,?\\s*(?:${UNIT_DESIGNATORS})\\.?\\s*[A-Za-z0-9-]+)?` +\n String.raw`(?:\\s*,\\s*[A-Za-z .'-]+,?\\s*(?:${US_STATES})\\s+\\d{5}(?:-\\d{4})?)?`,\n \"gi\",\n ),\n // 2. `PO Box 4242` / `P.O. Box 4242`.\n /\\bP\\.?\\s?O\\.?\\s?Box\\s+\\d{1,7}\\b/gi,\n // 3. Standalone city, state, ZIP tail: `Mountain View, CA 94043`.\n new RegExp(\n String.raw`\\b[A-Za-z .'-]+,\\s*(?:${US_STATES})\\s+\\d{5}(?:-\\d{4})?\\b`,\n \"g\",\n ),\n];\n\nconst DEFAULT_PHONE_PATTERNS: RegExp[] = [\n // 1. E.164 / international with leading `+`: `+44 20 7946 0958`,\n // `+1-415-555-0123`, `+442079460958`.\n /\\+\\d{1,3}(?:[\\s.-]?\\d{1,4}){1,5}\\b/g,\n // 2. NANP with explicit separators (a separator is REQUIRED between groups\n // so bare 10-digit runs survive): `(415) 555-0123`, `415-555-0123`,\n // `415.555.0123`, `415 555 0123`. No leading `\\b` before `(` — there is\n // no word boundary between a space and `(`.\n /(?:\\(\\d{3}\\)[\\s.-]?|\\b\\d{3}[\\s.-])\\d{3}[\\s.-]\\d{4}\\b/g,\n];\n\n/**\n * Geo coordinate redaction.\n *\n * The travel-time consumer now reads from the Location plugin\n * (`plugins/plugin-personal-assistant/src/travel-time/service.ts`), so precise lat/lon\n * values can land in trajectory text. We strip them before any export with\n * the marker `[REDACTED_GEO]`.\n *\n * Patterns are intentionally narrow — they require a lat/lng label, a JSON\n * wrapper, or at least one decimal place per number — so we do not redact\n * ordinary integer pairs (timestamps, IDs) that happen to be comma-separated.\n *\n * Order matters: the JSON `coords` block is consumed first so the inner\n * `latitude/longitude` pair does not get redacted twice.\n */\nconst GEO_REPLACEMENT = \"[REDACTED_GEO]\";\n\nconst DEFAULT_GEO_PATTERNS: RegExp[] = [\n // 1. JSON `\"coords\":{\"latitude\":..,\"longitude\":..[,...]}` (Capacitor shape).\n /\"coords\"\\s*:\\s*\\{\\s*\"latitude\"\\s*:\\s*-?\\d+(?:\\.\\d+)?\\s*,\\s*\"longitude\"\\s*:\\s*-?\\d+(?:\\.\\d+)?(?:\\s*,\\s*\"[A-Za-z_][A-Za-z0-9_]*\"\\s*:\\s*[^,}]+)*\\s*\\}/g,\n // 2. Bare JSON pair `\"latitude\":..,\"longitude\":..`.\n /\"latitude\"\\s*:\\s*-?\\d+(?:\\.\\d+)?\\s*,\\s*\"longitude\"\\s*:\\s*-?\\d+(?:\\.\\d+)?/g,\n // 3. `current location: 37.7, -122.4` / `coords: ...` / `coordinates=...`.\n /\\b(?:current\\s+location|location|coords|coordinates)\\s*[:=]\\s*-?\\d+(?:\\.\\d+)?\\s*,\\s*-?\\d+(?:\\.\\d+)?/gi,\n // 4. Labeled `lat: .., lng: ..` / `latitude=.., longitude=..`.\n /\\b(?:lat|latitude)\\s*[:=]\\s*-?\\d+(?:\\.\\d+)?\\s*[,;]\\s*(?:lng|lon|long|longitude)\\s*[:=]\\s*-?\\d+(?:\\.\\d+)?/gi,\n // 5. Bare decimal pair `37.7749, -122.4194` (both numbers must have a\n // fractional component to avoid matching integer pairs).\n /\\b-?\\d{1,3}\\.\\d{2,}\\s*,\\s*-?\\d{1,3}\\.\\d{2,}\\b/g,\n];\n\nfunction snapshotEnvCredentials(envKeys: string[]): string[] {\n // Heuristic: a key counts as a credential if its NAME matches a common\n // secret-marker substring AND its VALUE is non-empty and reasonably long.\n const interesting = /KEY|TOKEN|SECRET|PASSWORD|API|CREDENTIAL/i;\n const out: string[] = [];\n for (const key of envKeys) {\n if (!interesting.test(key)) continue;\n const value = process.env[key];\n if (typeof value !== \"string\") continue;\n if (value.length < 8) continue;\n out.push(value);\n }\n return out;\n}\n\ninterface InternalState {\n anonymizationCount: number;\n redactionCount: number;\n}\n\nfunction redactCredentials(\n value: string,\n patterns: Array<{ label: string; pattern: RegExp }>,\n credentialValues: string[],\n state: InternalState,\n): string {\n let out = value;\n for (const { label, pattern } of patterns) {\n out = out.replace(pattern, () => {\n state.redactionCount += 1;\n return `<REDACTED:${label}>`;\n });\n }\n for (const credValue of credentialValues) {\n if (!credValue) continue;\n const escaped = credValue.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const re = new RegExp(escaped, \"g\");\n out = out.replace(re, () => {\n state.redactionCount += 1;\n return \"<REDACTED:env-secret>\";\n });\n }\n return out;\n}\n\nfunction redactGeo(value: string, state: InternalState): string {\n let out = value;\n for (const pattern of DEFAULT_GEO_PATTERNS) {\n out = out.replace(pattern, () => {\n state.redactionCount += 1;\n return GEO_REPLACEMENT;\n });\n }\n return out;\n}\n\nfunction redactPii(value: string, state: InternalState): string {\n let out = value;\n // email → address → phone (see note on DEFAULT_PHONE_PATTERNS / addresses).\n out = out.replace(EMAIL_PATTERN, () => {\n state.redactionCount += 1;\n return EMAIL_REPLACEMENT;\n });\n for (const pattern of DEFAULT_ADDRESS_PATTERNS) {\n out = out.replace(pattern, () => {\n state.redactionCount += 1;\n return ADDRESS_REPLACEMENT;\n });\n }\n for (const pattern of DEFAULT_PHONE_PATTERNS) {\n out = out.replace(pattern, () => {\n state.redactionCount += 1;\n return PHONE_REPLACEMENT;\n });\n }\n return out;\n}\n\nfunction anonymizeHandles(\n value: string,\n options: PrivacyFilterOptions,\n state: InternalState,\n): { result: string; entityHits: Set<string> } {\n const platforms = options.platforms ?? DEFAULT_PLATFORMS;\n const entityHits = new Set<string>();\n if (!options.anonymizer) {\n return { result: value, entityHits };\n }\n\n const result = value.replace(HANDLE_PATTERN, (match, handle: string) => {\n const stripped = handle.startsWith(\"@\") ? handle.slice(1) : handle;\n for (const platform of platforms) {\n const entityId = options.anonymizer?.resolveEntityId(platform, stripped);\n if (entityId) {\n state.anonymizationCount += 1;\n entityHits.add(entityId);\n return `<entity:${entityId}>`;\n }\n }\n return match;\n });\n return { result, entityHits };\n}\n\nfunction transformText(\n value: string,\n options: PrivacyFilterOptions,\n credentialValues: string[],\n credentialPatterns: Array<{ label: string; pattern: RegExp }>,\n state: InternalState,\n collectedEntities: Set<string>,\n): string {\n // Geo first so JSON `coords` blocks collapse before any later pass can see\n // a stray decimal pair inside them.\n const geoRedacted = redactGeo(value, state);\n const credRedacted = redactCredentials(\n geoRedacted,\n credentialPatterns,\n credentialValues,\n state,\n );\n const piiRedacted = redactPii(credRedacted, state);\n const { result, entityHits } = anonymizeHandles(piiRedacted, options, state);\n for (const entityId of entityHits) collectedEntities.add(entityId);\n return result;\n}\n\n/**\n * Recursively transform every string contained in `value` (objects, arrays,\n * and nested combinations). Returns the same shape with strings rewritten.\n */\nfunction transformDeep(\n value: unknown,\n options: PrivacyFilterOptions,\n credentialValues: string[],\n credentialPatterns: Array<{ label: string; pattern: RegExp }>,\n state: InternalState,\n collectedEntities: Set<string>,\n): unknown {\n if (typeof value === \"string\") {\n return transformText(\n value,\n options,\n credentialValues,\n credentialPatterns,\n state,\n collectedEntities,\n );\n }\n if (Array.isArray(value)) {\n return value.map((entry) =>\n transformDeep(\n entry,\n options,\n credentialValues,\n credentialPatterns,\n state,\n collectedEntities,\n ),\n );\n }\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(\n value as Record<string, unknown>,\n )) {\n out[key] = transformDeep(\n entry,\n options,\n credentialValues,\n credentialPatterns,\n state,\n collectedEntities,\n );\n }\n return out;\n }\n return value;\n}\n\n/**\n * Apply the privacy filter to a list of trajectories. Returns the filtered\n * list with credential references redacted and platform handles replaced by\n * opaque entity IDs. Trajectories whose anonymized entities are marked as\n * `private` are dropped wholesale.\n */\nexport function applyPrivacyFilter<T extends FilterableTrajectory>(\n trajectories: T[],\n options: PrivacyFilterOptions = {},\n): FilterResult<T> {\n const credentialPatterns = [\n ...DEFAULT_CREDENTIAL_PATTERNS,\n ...(options.extraCredentialPatterns ?? []),\n ];\n const envKeys = options.envKeySnapshot ?? Object.keys(process.env);\n const credentialValues = snapshotEnvCredentials(envKeys);\n\n const dropped: Array<{ trajectoryId?: string; reason: string }> = [];\n const filtered: T[] = [];\n const state: InternalState = {\n anonymizationCount: 0,\n redactionCount: 0,\n };\n\n for (const trajectory of trajectories) {\n const trajectoryEntities = new Set<string>();\n const cloned = JSON.parse(JSON.stringify(trajectory)) as T;\n const steps = cloned.steps ?? [];\n for (const step of steps) {\n for (const call of step.llmCalls ?? []) {\n if (typeof call.systemPrompt === \"string\") {\n call.systemPrompt = transformText(\n call.systemPrompt,\n options,\n credentialValues,\n credentialPatterns,\n state,\n trajectoryEntities,\n );\n }\n if (typeof call.userPrompt === \"string\") {\n call.userPrompt = transformText(\n call.userPrompt,\n options,\n credentialValues,\n credentialPatterns,\n state,\n trajectoryEntities,\n );\n }\n if (typeof call.response === \"string\") {\n call.response = transformText(\n call.response,\n options,\n credentialValues,\n credentialPatterns,\n state,\n trajectoryEntities,\n );\n }\n }\n for (const access of step.providerAccesses ?? []) {\n if (access.data !== undefined) {\n access.data = transformDeep(\n access.data,\n options,\n credentialValues,\n credentialPatterns,\n state,\n trajectoryEntities,\n );\n }\n }\n }\n\n if (cloned.metadata && typeof cloned.metadata === \"object\") {\n cloned.metadata = transformDeep(\n cloned.metadata,\n options,\n credentialValues,\n credentialPatterns,\n state,\n trajectoryEntities,\n ) as Record<string, unknown>;\n }\n\n // Drop the whole trajectory if any participating entity is private.\n const lookup = options.anonymizer?.getPrivacyLevel;\n if (lookup) {\n let isPrivate = false;\n for (const entityId of trajectoryEntities) {\n if (lookup(entityId) === \"private\") {\n isPrivate = true;\n break;\n }\n }\n if (isPrivate) {\n dropped.push({\n trajectoryId: trajectory.trajectoryId,\n reason: \"entity-private\",\n });\n continue;\n }\n }\n\n filtered.push(cloned);\n }\n\n return {\n trajectories: filtered,\n dropped,\n redactionCount: state.redactionCount,\n anonymizationCount: state.anonymizationCount,\n };\n}\n\n/**\n * Stable, dependency-free anonymizer: maps a `(platform, handle)` pair to a\n * 16-hex-character opaque id via `SHA-256(salt:platform:handle)`. The same\n * handle always resolves to the same id (for a given salt), so cross-message\n * references stay linkable in the exported corpus while the real handle is\n * gone. Returns the id for every handle (never `null`), so all `@mentions`\n * get anonymized.\n */\nexport function createHashAnonymizer(salt = \"\"): AnonymizerLookup {\n return {\n resolveEntityId(platform: string, handle: string): string {\n return createHash(\"sha256\")\n .update(`${salt}:${platform.toLowerCase()}:${handle.toLowerCase()}`)\n .digest(\"hex\")\n .slice(0, 16);\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * A/B promotion gate for optimized prompt artifacts.
3
+ *
4
+ * Native MIPRO/GEPA/bootstrap-fewshot runs in `backends/native.ts` produce a
5
+ * candidate prompt for a task. Without a gate, that candidate is written as the
6
+ * `current` artifact unconditionally, which lets noisy single-run scores
7
+ * silently regress production prompts.
8
+ *
9
+ * This module evaluates a candidate against its incumbent (the prompt currently
10
+ * loaded by `OptimizedPromptService` — or the baseline when no artifact exists
11
+ * yet) on a held-out trajectory replay set, and only promotes when the
12
+ * candidate's score exceeds the incumbent score by more than the expected
13
+ * scoring noise (default: 1.5× the standard deviation of incumbent scores
14
+ * across reseeded scoring runs).
15
+ *
16
+ * Inputs and outputs are pure JS objects — no filesystem, no service lookups.
17
+ * The orchestrator passes incumbent text + dataset in, gets a structured
18
+ * decision back, and is responsible for persistence (promote → write artifact;
19
+ * reject → write `candidate_rejected_<timestamp>.json`).
20
+ */
21
+ import type { OptimizationExample, PromptScorer } from "../optimizers/types.js";
22
+ /**
23
+ * Default noise threshold. A candidate must beat the incumbent's mean by
24
+ * `noiseThreshold × stddev(incumbent)` to be promoted. 1.5× is the same
25
+ * multiplier the MIPRO paper uses for its variance-aware acceptance test.
26
+ */
27
+ export declare const DEFAULT_NOISE_THRESHOLD = 1.5;
28
+ /**
29
+ * Default number of times the incumbent is re-scored to estimate scoring noise.
30
+ * Each pass uses a fresh subsample (when `reseedSubsample` is set) so the
31
+ * resulting stddev captures both sampling jitter and scorer non-determinism.
32
+ */
33
+ export declare const DEFAULT_INCUMBENT_RESEEDS = 3;
34
+ export interface PromotionGateOptions {
35
+ /**
36
+ * Multiplier applied to the incumbent stddev. Default `1.5`.
37
+ * Set to 0 to promote on any positive delta (not recommended).
38
+ */
39
+ noiseThreshold?: number;
40
+ /**
41
+ * Reseeded incumbent scoring passes. Default `3`. Each pass produces an
42
+ * independent score on a fresh subsample (when `scoringSubset` is set), or on
43
+ * the full dataset otherwise. More passes → tighter stddev estimate at the
44
+ * cost of more model calls.
45
+ */
46
+ incumbentReseeds?: number;
47
+ /**
48
+ * Cap on examples scored per pass. When set, each incumbent reseed and the
49
+ * single candidate pass each draw their own subsample. Defaults to all rows.
50
+ */
51
+ scoringSubset?: number;
52
+ /**
53
+ * Deterministic RNG override (tests). Defaults to `Math.random`.
54
+ */
55
+ rng?: () => number;
56
+ /**
57
+ * When true, the candidate is scored on a freshly subsampled held-out set
58
+ * (independent of the incumbent reseed samples). Default: true. Set false to
59
+ * reuse the union of all incumbent samples.
60
+ */
61
+ reseedCandidate?: boolean;
62
+ }
63
+ export interface PromotionGateInput {
64
+ /** Prompt currently in production for this task. */
65
+ incumbentPrompt: string;
66
+ /** Candidate prompt produced by the optimizer for this task. */
67
+ candidatePrompt: string;
68
+ /** Replay dataset — same shape the optimizers consume. */
69
+ dataset: OptimizationExample[];
70
+ /** Scorer used for both incumbent and candidate. */
71
+ scorer: PromptScorer;
72
+ options?: PromotionGateOptions;
73
+ }
74
+ export interface PromotionDecision {
75
+ /** `true` when the candidate should replace the incumbent. */
76
+ promote: boolean;
77
+ /** Mean score of the incumbent across reseeded passes. */
78
+ incumbentMeanScore: number;
79
+ /** Stddev of incumbent scores across reseeded passes. */
80
+ incumbentStdDev: number;
81
+ /** Score of the candidate on its (re)sampled held-out set. */
82
+ candidateScore: number;
83
+ /** `candidateScore - incumbentMeanScore`. Positive = candidate better. */
84
+ delta: number;
85
+ /** `noiseThreshold * incumbentStdDev`. Candidate must beat this. */
86
+ promotionMargin: number;
87
+ /** Multiplier used (`options.noiseThreshold ?? DEFAULT_NOISE_THRESHOLD`). */
88
+ noiseThreshold: number;
89
+ /** Number of reseeded incumbent passes actually run. */
90
+ incumbentReseeds: number;
91
+ /** Number of rows scored per pass. */
92
+ examplesPerPass: number;
93
+ /** Plain-english reason describing why the gate accepted or rejected. */
94
+ reason: string;
95
+ /** Raw per-pass incumbent scores, oldest first. */
96
+ incumbentScores: number[];
97
+ }
98
+ /**
99
+ * Evaluate whether a candidate prompt should be promoted over the incumbent.
100
+ *
101
+ * Algorithm:
102
+ * 1. Score the incumbent `incumbentReseeds` times on independently subsampled
103
+ * held-out sets (or the full dataset when `scoringSubset` is unset).
104
+ * 2. Compute the mean and population stddev of the resulting scores.
105
+ * 3. Score the candidate once on a fresh subsample (or the full dataset).
106
+ * 4. Promote only when `candidateScore > incumbentMean + noiseThreshold * stddev`.
107
+ *
108
+ * Edge cases:
109
+ * - Empty dataset → never promote (delta=0, candidate cannot demonstrate
110
+ * improvement).
111
+ * - `incumbentReseeds < 1` → rejected outright; we need at least one
112
+ * measurement to gate against.
113
+ * - When all incumbent passes return the exact same score, stddev=0 and the
114
+ * candidate only needs to strictly exceed the incumbent mean.
115
+ */
116
+ export declare function evaluatePromotion(input: PromotionGateInput): Promise<PromotionDecision>;
117
+ //# sourceMappingURL=promotion-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promotion-gate.d.ts","sourceRoot":"","sources":["../../src/core/promotion-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEhF;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAE3C;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAE3C,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,oDAAoD;IACpD,eAAe,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,eAAe,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,oDAAoD;IACpD,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,8DAA8D;IAC9D,OAAO,EAAE,OAAO,CAAC;IACjB,0DAA0D;IAC1D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,yDAAyD;IACzD,eAAe,EAAE,MAAM,CAAC;IACxB,8DAA8D;IAC9D,cAAc,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,eAAe,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,cAAc,EAAE,MAAM,CAAC;IACvB,wDAAwD;IACxD,gBAAgB,EAAE,MAAM,CAAC;IACzB,sCAAsC;IACtC,eAAe,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,iBAAiB,CAAC,CA+E5B"}
@@ -0,0 +1,85 @@
1
+ import { subsample } from "../optimizers/scoring.js";
2
+ const DEFAULT_NOISE_THRESHOLD = 1.5;
3
+ const DEFAULT_INCUMBENT_RESEEDS = 3;
4
+ async function evaluatePromotion(input) {
5
+ const noiseThreshold = input.options?.noiseThreshold ?? DEFAULT_NOISE_THRESHOLD;
6
+ const incumbentReseeds = Math.max(
7
+ 1,
8
+ input.options?.incumbentReseeds ?? DEFAULT_INCUMBENT_RESEEDS
9
+ );
10
+ const rng = input.options?.rng ?? Math.random;
11
+ const reseedCandidate = input.options?.reseedCandidate ?? true;
12
+ const examplesPerPass = typeof input.options?.scoringSubset === "number" ? Math.min(input.options.scoringSubset, input.dataset.length) : input.dataset.length;
13
+ if (input.dataset.length === 0 || examplesPerPass === 0) {
14
+ return {
15
+ promote: false,
16
+ incumbentMeanScore: 0,
17
+ incumbentStdDev: 0,
18
+ candidateScore: 0,
19
+ delta: 0,
20
+ promotionMargin: 0,
21
+ noiseThreshold,
22
+ incumbentReseeds: 0,
23
+ examplesPerPass: 0,
24
+ reason: "dataset is empty; cannot evaluate promotion",
25
+ incumbentScores: []
26
+ };
27
+ }
28
+ const incumbentScores = [];
29
+ let lastIncumbentSample = null;
30
+ for (let i = 0; i < incumbentReseeds; i += 1) {
31
+ const sample = drawSample(input.dataset, examplesPerPass, rng);
32
+ const score = await input.scorer(input.incumbentPrompt, sample);
33
+ incumbentScores.push(score);
34
+ lastIncumbentSample = sample;
35
+ }
36
+ const incumbentMean = mean(incumbentScores);
37
+ const incumbentStd = populationStdDev(incumbentScores, incumbentMean);
38
+ const candidateSample = reseedCandidate || !lastIncumbentSample ? drawSample(input.dataset, examplesPerPass, rng) : lastIncumbentSample;
39
+ const candidateScore = await input.scorer(
40
+ input.candidatePrompt,
41
+ candidateSample
42
+ );
43
+ const promotionMargin = noiseThreshold * incumbentStd;
44
+ const delta = candidateScore - incumbentMean;
45
+ const promote = delta > promotionMargin;
46
+ const reason = promote ? `candidate beats incumbent by ${delta.toFixed(4)} > margin ${promotionMargin.toFixed(4)} (${noiseThreshold}\xD7 stddev=${incumbentStd.toFixed(4)})` : delta <= 0 ? `candidate did not improve over incumbent (delta=${delta.toFixed(4)})` : `candidate improvement ${delta.toFixed(4)} did not exceed noise margin ${promotionMargin.toFixed(4)} (${noiseThreshold}\xD7 stddev=${incumbentStd.toFixed(4)})`;
47
+ return {
48
+ promote,
49
+ incumbentMeanScore: incumbentMean,
50
+ incumbentStdDev: incumbentStd,
51
+ candidateScore,
52
+ delta,
53
+ promotionMargin,
54
+ noiseThreshold,
55
+ incumbentReseeds,
56
+ examplesPerPass,
57
+ reason,
58
+ incumbentScores
59
+ };
60
+ }
61
+ function drawSample(dataset, count, rng) {
62
+ if (count >= dataset.length) return [...dataset];
63
+ return subsample(dataset, count, rng);
64
+ }
65
+ function mean(values) {
66
+ if (values.length === 0) return 0;
67
+ let total = 0;
68
+ for (const v of values) total += v;
69
+ return total / values.length;
70
+ }
71
+ function populationStdDev(values, precomputedMean) {
72
+ if (values.length === 0) return 0;
73
+ let total = 0;
74
+ for (const v of values) {
75
+ const d = v - precomputedMean;
76
+ total += d * d;
77
+ }
78
+ return Math.sqrt(total / values.length);
79
+ }
80
+ export {
81
+ DEFAULT_INCUMBENT_RESEEDS,
82
+ DEFAULT_NOISE_THRESHOLD,
83
+ evaluatePromotion
84
+ };
85
+ //# sourceMappingURL=promotion-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/promotion-gate.ts"],"sourcesContent":["/**\n * A/B promotion gate for optimized prompt artifacts.\n *\n * Native MIPRO/GEPA/bootstrap-fewshot runs in `backends/native.ts` produce a\n * candidate prompt for a task. Without a gate, that candidate is written as the\n * `current` artifact unconditionally, which lets noisy single-run scores\n * silently regress production prompts.\n *\n * This module evaluates a candidate against its incumbent (the prompt currently\n * loaded by `OptimizedPromptService` — or the baseline when no artifact exists\n * yet) on a held-out trajectory replay set, and only promotes when the\n * candidate's score exceeds the incumbent score by more than the expected\n * scoring noise (default: 1.5× the standard deviation of incumbent scores\n * across reseeded scoring runs).\n *\n * Inputs and outputs are pure JS objects — no filesystem, no service lookups.\n * The orchestrator passes incumbent text + dataset in, gets a structured\n * decision back, and is responsible for persistence (promote → write artifact;\n * reject → write `candidate_rejected_<timestamp>.json`).\n */\n\nimport { subsample } from \"../optimizers/scoring.js\";\nimport type { OptimizationExample, PromptScorer } from \"../optimizers/types.js\";\n\n/**\n * Default noise threshold. A candidate must beat the incumbent's mean by\n * `noiseThreshold × stddev(incumbent)` to be promoted. 1.5× is the same\n * multiplier the MIPRO paper uses for its variance-aware acceptance test.\n */\nexport const DEFAULT_NOISE_THRESHOLD = 1.5;\n\n/**\n * Default number of times the incumbent is re-scored to estimate scoring noise.\n * Each pass uses a fresh subsample (when `reseedSubsample` is set) so the\n * resulting stddev captures both sampling jitter and scorer non-determinism.\n */\nexport const DEFAULT_INCUMBENT_RESEEDS = 3;\n\nexport interface PromotionGateOptions {\n /**\n * Multiplier applied to the incumbent stddev. Default `1.5`.\n * Set to 0 to promote on any positive delta (not recommended).\n */\n noiseThreshold?: number;\n /**\n * Reseeded incumbent scoring passes. Default `3`. Each pass produces an\n * independent score on a fresh subsample (when `scoringSubset` is set), or on\n * the full dataset otherwise. More passes → tighter stddev estimate at the\n * cost of more model calls.\n */\n incumbentReseeds?: number;\n /**\n * Cap on examples scored per pass. When set, each incumbent reseed and the\n * single candidate pass each draw their own subsample. Defaults to all rows.\n */\n scoringSubset?: number;\n /**\n * Deterministic RNG override (tests). Defaults to `Math.random`.\n */\n rng?: () => number;\n /**\n * When true, the candidate is scored on a freshly subsampled held-out set\n * (independent of the incumbent reseed samples). Default: true. Set false to\n * reuse the union of all incumbent samples.\n */\n reseedCandidate?: boolean;\n}\n\nexport interface PromotionGateInput {\n /** Prompt currently in production for this task. */\n incumbentPrompt: string;\n /** Candidate prompt produced by the optimizer for this task. */\n candidatePrompt: string;\n /** Replay dataset — same shape the optimizers consume. */\n dataset: OptimizationExample[];\n /** Scorer used for both incumbent and candidate. */\n scorer: PromptScorer;\n options?: PromotionGateOptions;\n}\n\nexport interface PromotionDecision {\n /** `true` when the candidate should replace the incumbent. */\n promote: boolean;\n /** Mean score of the incumbent across reseeded passes. */\n incumbentMeanScore: number;\n /** Stddev of incumbent scores across reseeded passes. */\n incumbentStdDev: number;\n /** Score of the candidate on its (re)sampled held-out set. */\n candidateScore: number;\n /** `candidateScore - incumbentMeanScore`. Positive = candidate better. */\n delta: number;\n /** `noiseThreshold * incumbentStdDev`. Candidate must beat this. */\n promotionMargin: number;\n /** Multiplier used (`options.noiseThreshold ?? DEFAULT_NOISE_THRESHOLD`). */\n noiseThreshold: number;\n /** Number of reseeded incumbent passes actually run. */\n incumbentReseeds: number;\n /** Number of rows scored per pass. */\n examplesPerPass: number;\n /** Plain-english reason describing why the gate accepted or rejected. */\n reason: string;\n /** Raw per-pass incumbent scores, oldest first. */\n incumbentScores: number[];\n}\n\n/**\n * Evaluate whether a candidate prompt should be promoted over the incumbent.\n *\n * Algorithm:\n * 1. Score the incumbent `incumbentReseeds` times on independently subsampled\n * held-out sets (or the full dataset when `scoringSubset` is unset).\n * 2. Compute the mean and population stddev of the resulting scores.\n * 3. Score the candidate once on a fresh subsample (or the full dataset).\n * 4. Promote only when `candidateScore > incumbentMean + noiseThreshold * stddev`.\n *\n * Edge cases:\n * - Empty dataset → never promote (delta=0, candidate cannot demonstrate\n * improvement).\n * - `incumbentReseeds < 1` → rejected outright; we need at least one\n * measurement to gate against.\n * - When all incumbent passes return the exact same score, stddev=0 and the\n * candidate only needs to strictly exceed the incumbent mean.\n */\nexport async function evaluatePromotion(\n input: PromotionGateInput,\n): Promise<PromotionDecision> {\n const noiseThreshold =\n input.options?.noiseThreshold ?? DEFAULT_NOISE_THRESHOLD;\n const incumbentReseeds = Math.max(\n 1,\n input.options?.incumbentReseeds ?? DEFAULT_INCUMBENT_RESEEDS,\n );\n const rng = input.options?.rng ?? Math.random;\n const reseedCandidate = input.options?.reseedCandidate ?? true;\n const examplesPerPass =\n typeof input.options?.scoringSubset === \"number\"\n ? Math.min(input.options.scoringSubset, input.dataset.length)\n : input.dataset.length;\n\n if (input.dataset.length === 0 || examplesPerPass === 0) {\n return {\n promote: false,\n incumbentMeanScore: 0,\n incumbentStdDev: 0,\n candidateScore: 0,\n delta: 0,\n promotionMargin: 0,\n noiseThreshold,\n incumbentReseeds: 0,\n examplesPerPass: 0,\n reason: \"dataset is empty; cannot evaluate promotion\",\n incumbentScores: [],\n };\n }\n\n const incumbentScores: number[] = [];\n let lastIncumbentSample: OptimizationExample[] | null = null;\n for (let i = 0; i < incumbentReseeds; i += 1) {\n const sample = drawSample(input.dataset, examplesPerPass, rng);\n const score = await input.scorer(input.incumbentPrompt, sample);\n incumbentScores.push(score);\n lastIncumbentSample = sample;\n }\n\n const incumbentMean = mean(incumbentScores);\n const incumbentStd = populationStdDev(incumbentScores, incumbentMean);\n\n // When `reseedCandidate` is false the candidate is scored on the exact same\n // examples the final incumbent pass saw — useful for direct A/B comparison\n // without sampling jitter. Defaults to true (fresh subsample) so the\n // candidate score is independent of which rows happened to land in the last\n // incumbent reseed.\n const candidateSample =\n reseedCandidate || !lastIncumbentSample\n ? drawSample(input.dataset, examplesPerPass, rng)\n : lastIncumbentSample;\n const candidateScore = await input.scorer(\n input.candidatePrompt,\n candidateSample,\n );\n\n const promotionMargin = noiseThreshold * incumbentStd;\n const delta = candidateScore - incumbentMean;\n const promote = delta > promotionMargin;\n\n const reason = promote\n ? `candidate beats incumbent by ${delta.toFixed(4)} > margin ${promotionMargin.toFixed(4)} (${noiseThreshold}× stddev=${incumbentStd.toFixed(4)})`\n : delta <= 0\n ? `candidate did not improve over incumbent (delta=${delta.toFixed(4)})`\n : `candidate improvement ${delta.toFixed(4)} did not exceed noise margin ${promotionMargin.toFixed(4)} (${noiseThreshold}× stddev=${incumbentStd.toFixed(4)})`;\n\n return {\n promote,\n incumbentMeanScore: incumbentMean,\n incumbentStdDev: incumbentStd,\n candidateScore,\n delta,\n promotionMargin,\n noiseThreshold,\n incumbentReseeds,\n examplesPerPass,\n reason,\n incumbentScores,\n };\n}\n\nfunction drawSample(\n dataset: OptimizationExample[],\n count: number,\n rng: () => number,\n): OptimizationExample[] {\n if (count >= dataset.length) return [...dataset];\n return subsample(dataset, count, rng);\n}\n\nfunction mean(values: number[]): number {\n if (values.length === 0) return 0;\n let total = 0;\n for (const v of values) total += v;\n return total / values.length;\n}\n\n/**\n * Population stddev (divide by N, not N-1). With small N (default 3 reseeds)\n * the sample stddev estimator inflates noise enough that even slightly better\n * candidates get rejected; the gate is already conservative through the\n * `noiseThreshold` multiplier, so we use the population form here.\n */\nfunction populationStdDev(values: number[], precomputedMean: number): number {\n if (values.length === 0) return 0;\n let total = 0;\n for (const v of values) {\n const d = v - precomputedMean;\n total += d * d;\n }\n return Math.sqrt(total / values.length);\n}\n"],"mappings":"AAqBA,SAAS,iBAAiB;AAQnB,MAAM,0BAA0B;AAOhC,MAAM,4BAA4B;AAuFzC,eAAsB,kBACpB,OAC4B;AAC5B,QAAM,iBACJ,MAAM,SAAS,kBAAkB;AACnC,QAAM,mBAAmB,KAAK;AAAA,IAC5B;AAAA,IACA,MAAM,SAAS,oBAAoB;AAAA,EACrC;AACA,QAAM,MAAM,MAAM,SAAS,OAAO,KAAK;AACvC,QAAM,kBAAkB,MAAM,SAAS,mBAAmB;AAC1D,QAAM,kBACJ,OAAO,MAAM,SAAS,kBAAkB,WACpC,KAAK,IAAI,MAAM,QAAQ,eAAe,MAAM,QAAQ,MAAM,IAC1D,MAAM,QAAQ;AAEpB,MAAI,MAAM,QAAQ,WAAW,KAAK,oBAAoB,GAAG;AACvD,WAAO;AAAA,MACL,SAAS;AAAA,MACT,oBAAoB;AAAA,MACpB,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,MAChB,OAAO;AAAA,MACP,iBAAiB;AAAA,MACjB;AAAA,MACA,kBAAkB;AAAA,MAClB,iBAAiB;AAAA,MACjB,QAAQ;AAAA,MACR,iBAAiB,CAAC;AAAA,IACpB;AAAA,EACF;AAEA,QAAM,kBAA4B,CAAC;AACnC,MAAI,sBAAoD;AACxD,WAAS,IAAI,GAAG,IAAI,kBAAkB,KAAK,GAAG;AAC5C,UAAM,SAAS,WAAW,MAAM,SAAS,iBAAiB,GAAG;AAC7D,UAAM,QAAQ,MAAM,MAAM,OAAO,MAAM,iBAAiB,MAAM;AAC9D,oBAAgB,KAAK,KAAK;AAC1B,0BAAsB;AAAA,EACxB;AAEA,QAAM,gBAAgB,KAAK,eAAe;AAC1C,QAAM,eAAe,iBAAiB,iBAAiB,aAAa;AAOpE,QAAM,kBACJ,mBAAmB,CAAC,sBAChB,WAAW,MAAM,SAAS,iBAAiB,GAAG,IAC9C;AACN,QAAM,iBAAiB,MAAM,MAAM;AAAA,IACjC,MAAM;AAAA,IACN;AAAA,EACF;AAEA,QAAM,kBAAkB,iBAAiB;AACzC,QAAM,QAAQ,iBAAiB;AAC/B,QAAM,UAAU,QAAQ;AAExB,QAAM,SAAS,UACX,gCAAgC,MAAM,QAAQ,CAAC,CAAC,aAAa,gBAAgB,QAAQ,CAAC,CAAC,KAAK,cAAc,eAAY,aAAa,QAAQ,CAAC,CAAC,MAC7I,SAAS,IACP,mDAAmD,MAAM,QAAQ,CAAC,CAAC,MACnE,yBAAyB,MAAM,QAAQ,CAAC,CAAC,gCAAgC,gBAAgB,QAAQ,CAAC,CAAC,KAAK,cAAc,eAAY,aAAa,QAAQ,CAAC,CAAC;AAE/J,SAAO;AAAA,IACL;AAAA,IACA,oBAAoB;AAAA,IACpB,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,WACP,SACA,OACA,KACuB;AACvB,MAAI,SAAS,QAAQ,OAAQ,QAAO,CAAC,GAAG,OAAO;AAC/C,SAAO,UAAU,SAAS,OAAO,GAAG;AACtC;AAEA,SAAS,KAAK,QAA0B;AACtC,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,MAAI,QAAQ;AACZ,aAAW,KAAK,OAAQ,UAAS;AACjC,SAAO,QAAQ,OAAO;AACxB;AAQA,SAAS,iBAAiB,QAAkB,iBAAiC;AAC3E,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,MAAI,QAAQ;AACZ,aAAW,KAAK,QAAQ;AACtB,UAAM,IAAI,IAAI;AACd,aAAS,IAAI;AAAA,EACf;AACA,SAAO,KAAK,KAAK,QAAQ,OAAO,MAAM;AACxC;","names":[]}