niceeval 0.13.2 → 0.13.3

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 (494) hide show
  1. package/dist/agents/bub.cjs +4 -4
  2. package/dist/agents/bub.cjs.map +1 -1
  3. package/dist/agents/bub.d.cts +2 -2
  4. package/dist/agents/bub.d.mts +2 -2
  5. package/dist/agents/bub.d.ts +2 -2
  6. package/dist/agents/claude-code-native.cjs +213 -0
  7. package/dist/agents/claude-code-native.cjs.map +1 -0
  8. package/dist/agents/claude-code-native.d.cts +5 -0
  9. package/dist/agents/claude-code-native.d.mts +5 -0
  10. package/dist/agents/claude-code-native.d.ts +5 -0
  11. package/dist/agents/claude-code-native.js +6 -0
  12. package/dist/agents/claude-code-native.js.map +1 -0
  13. package/dist/agents/claude-code.cjs +26 -80
  14. package/dist/agents/claude-code.cjs.map +1 -1
  15. package/dist/agents/codex-app-server.cjs +314 -0
  16. package/dist/agents/codex-app-server.cjs.map +1 -0
  17. package/dist/agents/codex-app-server.d.cts +9 -0
  18. package/dist/agents/codex-app-server.d.mts +9 -0
  19. package/dist/agents/codex-app-server.d.ts +9 -0
  20. package/dist/agents/codex-app-server.js +6 -0
  21. package/dist/agents/codex-app-server.js.map +1 -0
  22. package/dist/agents/codex.cjs +19 -68
  23. package/dist/agents/codex.cjs.map +1 -1
  24. package/dist/agents/codex.d.cts +3 -3
  25. package/dist/agents/codex.d.mts +3 -3
  26. package/dist/agents/codex.d.ts +3 -3
  27. package/dist/agents/index.cjs +3 -1
  28. package/dist/agents/index.cjs.map +1 -1
  29. package/dist/agents/index.d.cts +1 -0
  30. package/dist/agents/index.d.mts +1 -0
  31. package/dist/agents/index.d.ts +1 -0
  32. package/dist/agents/index.js +84 -82
  33. package/dist/agents/index.mjs +84 -82
  34. package/dist/agents/managed-jsonl.cjs +207 -0
  35. package/dist/agents/managed-jsonl.cjs.map +1 -0
  36. package/dist/agents/managed-jsonl.d.cts +39 -0
  37. package/dist/agents/managed-jsonl.d.mts +39 -0
  38. package/dist/agents/managed-jsonl.d.ts +39 -0
  39. package/dist/agents/managed-jsonl.js +6 -0
  40. package/dist/agents/managed-jsonl.js.map +1 -0
  41. package/dist/agents/managed-process.cjs +31 -0
  42. package/dist/agents/managed-process.cjs.map +1 -0
  43. package/dist/agents/managed-process.d.cts +3 -0
  44. package/dist/agents/managed-process.d.mts +3 -0
  45. package/dist/agents/managed-process.d.ts +3 -0
  46. package/dist/agents/managed-process.js +6 -0
  47. package/dist/agents/managed-process.js.map +1 -0
  48. package/dist/agents/omp.cjs +18 -1
  49. package/dist/agents/omp.cjs.map +1 -1
  50. package/dist/agents/sdk-streams.cjs +11 -4
  51. package/dist/agents/sdk-streams.cjs.map +1 -1
  52. package/dist/agents/types.cjs.map +1 -1
  53. package/dist/agents/types.d.cts +8 -0
  54. package/dist/agents/types.d.mts +8 -0
  55. package/dist/agents/types.d.ts +8 -0
  56. package/dist/analysis/bindings.cjs +74 -19
  57. package/dist/analysis/bindings.cjs.map +1 -1
  58. package/dist/analysis/bindings.d.cts +534 -156
  59. package/dist/analysis/bindings.d.mts +534 -156
  60. package/dist/analysis/bindings.d.ts +534 -156
  61. package/dist/analysis/domain-view.cjs.map +1 -1
  62. package/dist/analysis/domain-view.d.cts +137 -8
  63. package/dist/analysis/domain-view.d.mts +137 -8
  64. package/dist/analysis/domain-view.d.ts +137 -8
  65. package/dist/analysis/index.cjs.map +1 -1
  66. package/dist/analysis/index.d.cts +1 -1
  67. package/dist/analysis/index.d.mts +1 -1
  68. package/dist/analysis/index.d.ts +1 -1
  69. package/dist/assertions/api.cjs.map +1 -1
  70. package/dist/assertions/api.d.cts +62 -0
  71. package/dist/assertions/api.d.mts +62 -0
  72. package/dist/assertions/api.d.ts +62 -0
  73. package/dist/assertions/judge.cjs +56 -5
  74. package/dist/assertions/judge.cjs.map +1 -1
  75. package/dist/assertions/match.cjs +66 -42
  76. package/dist/assertions/match.cjs.map +1 -1
  77. package/dist/assertions/match.d.cts +13 -0
  78. package/dist/assertions/match.d.mts +13 -0
  79. package/dist/assertions/match.d.ts +13 -0
  80. package/dist/assertions/record/attachment.cjs +104 -33
  81. package/dist/assertions/record/attachment.cjs.map +1 -1
  82. package/dist/assertions/record/attachment.d.cts +294 -57
  83. package/dist/assertions/record/attachment.d.mts +294 -57
  84. package/dist/assertions/record/attachment.d.ts +294 -57
  85. package/dist/assertions/record/codec.cjs +91 -17
  86. package/dist/assertions/record/codec.cjs.map +1 -1
  87. package/dist/assertions/record/codec.d.cts +280 -52
  88. package/dist/assertions/record/codec.d.mts +280 -52
  89. package/dist/assertions/record/codec.d.ts +280 -52
  90. package/dist/assertions/record/index.js +2 -0
  91. package/dist/assertions/record/model.cjs +16 -0
  92. package/dist/assertions/record/model.cjs.map +1 -1
  93. package/dist/assertions/record/model.d.cts +120 -13
  94. package/dist/assertions/record/model.d.mts +120 -13
  95. package/dist/assertions/record/model.d.ts +120 -13
  96. package/dist/assertions/record/model.js +2 -0
  97. package/dist/assertions/record/producer.cjs +70 -50
  98. package/dist/assertions/record/producer.cjs.map +1 -1
  99. package/dist/assertions/record/producer.d.cts +8 -7
  100. package/dist/assertions/record/producer.d.mts +8 -7
  101. package/dist/assertions/record/producer.d.ts +8 -7
  102. package/dist/assertions/runtime.cjs +49 -7
  103. package/dist/assertions/runtime.cjs.map +1 -1
  104. package/dist/cli/application.cjs +40 -0
  105. package/dist/cli/application.cjs.map +1 -0
  106. package/dist/cli/application.d.cts +129 -0
  107. package/dist/cli/application.d.mts +129 -0
  108. package/dist/cli/application.d.ts +129 -0
  109. package/dist/cli/application.js +24 -0
  110. package/dist/cli/application.js.map +1 -0
  111. package/dist/cli/bootstrap.cjs +10 -4
  112. package/dist/cli/bootstrap.cjs.map +1 -1
  113. package/dist/cli/node-application.cjs +123 -0
  114. package/dist/cli/node-application.cjs.map +1 -0
  115. package/dist/cli/node-application.d.cts +14 -0
  116. package/dist/cli/node-application.d.mts +14 -0
  117. package/dist/cli/node-application.d.ts +14 -0
  118. package/dist/cli/node-application.js +26 -0
  119. package/dist/cli/node-application.js.map +1 -0
  120. package/dist/cli/program.cjs +2807 -0
  121. package/dist/cli/program.cjs.map +1 -0
  122. package/dist/cli/program.d.cts +352 -0
  123. package/dist/cli/program.d.mts +352 -0
  124. package/dist/cli/program.d.ts +352 -0
  125. package/dist/cli/program.js +36 -0
  126. package/dist/cli/program.js.map +1 -0
  127. package/dist/cli/project-configuration.cjs +134 -0
  128. package/dist/cli/project-configuration.cjs.map +1 -0
  129. package/dist/cli/project-configuration.d.cts +64 -0
  130. package/dist/cli/project-configuration.d.mts +64 -0
  131. package/dist/cli/project-configuration.d.ts +64 -0
  132. package/dist/cli/project-configuration.js +22 -0
  133. package/dist/cli/project-configuration.js.map +1 -0
  134. package/dist/cli/record.cjs +20 -4
  135. package/dist/cli/record.cjs.map +1 -1
  136. package/dist/cli/record.d.cts +3 -3
  137. package/dist/cli/record.d.mts +3 -3
  138. package/dist/cli/record.d.ts +3 -3
  139. package/dist/cli.cjs +19 -3130
  140. package/dist/cli.cjs.map +1 -1
  141. package/dist/cli.d.cts +2 -148
  142. package/dist/cli.d.mts +2 -148
  143. package/dist/cli.d.ts +2 -148
  144. package/dist/cli.js +22 -30
  145. package/dist/context/assert-first.cjs +21 -15
  146. package/dist/context/assert-first.cjs.map +1 -1
  147. package/dist/context/assert-first.d.cts +1 -0
  148. package/dist/context/assert-first.d.mts +1 -0
  149. package/dist/context/assert-first.d.ts +1 -0
  150. package/dist/context/attempt-resources.cjs +13 -0
  151. package/dist/context/attempt-resources.cjs.map +1 -0
  152. package/dist/context/attempt-resources.d.cts +3 -0
  153. package/dist/context/attempt-resources.d.mts +3 -0
  154. package/dist/context/attempt-resources.d.ts +3 -0
  155. package/dist/context/attempt-resources.js +8 -0
  156. package/dist/context/attempt-resources.js.map +1 -0
  157. package/dist/context/session.cjs +4 -3
  158. package/dist/context/session.cjs.map +1 -1
  159. package/dist/context/session.d.cts +1 -0
  160. package/dist/context/session.d.mts +1 -0
  161. package/dist/context/session.d.ts +1 -0
  162. package/dist/eval/record/verdict.cjs +3 -2
  163. package/dist/eval/record/verdict.cjs.map +1 -1
  164. package/dist/experiment/host/index.cjs +1 -6
  165. package/dist/experiment/host/index.cjs.map +1 -1
  166. package/dist/experiment/host/index.d.cts +1 -0
  167. package/dist/experiment/host/index.d.mts +1 -0
  168. package/dist/experiment/host/index.d.ts +1 -0
  169. package/dist/load-config.cjs +13 -4
  170. package/dist/load-config.cjs.map +1 -1
  171. package/dist/load-config.d.cts +4 -3
  172. package/dist/load-config.d.mts +4 -3
  173. package/dist/load-config.d.ts +4 -3
  174. package/dist/load-config.js +4 -2
  175. package/dist/o11y/parsers/codex.cjs +29 -7
  176. package/dist/o11y/parsers/codex.cjs.map +1 -1
  177. package/dist/record/codec/core.d.cts +0 -2
  178. package/dist/record/codec/core.d.mts +0 -2
  179. package/dist/record/codec/core.d.ts +0 -2
  180. package/dist/record/codec/identifiers.cjs +2 -3
  181. package/dist/record/codec/identifiers.cjs.map +1 -1
  182. package/dist/record/codec/identifiers.d.cts +1 -2
  183. package/dist/record/codec/identifiers.d.mts +1 -2
  184. package/dist/record/codec/identifiers.d.ts +1 -2
  185. package/dist/record/codec/identifiers.js +10 -12
  186. package/dist/record/codec/index.js +42 -44
  187. package/dist/record/definition/attachment.cjs +1 -0
  188. package/dist/record/definition/attachment.cjs.map +1 -1
  189. package/dist/record/definition/attachment.d.cts +14 -0
  190. package/dist/record/definition/attachment.d.mts +14 -0
  191. package/dist/record/definition/attachment.d.ts +14 -0
  192. package/dist/record/family/assertions-v1.cjs +104 -0
  193. package/dist/record/family/assertions-v1.cjs.map +1 -0
  194. package/dist/record/family/assertions-v1.d.cts +2 -0
  195. package/dist/record/family/assertions-v1.d.mts +2 -0
  196. package/dist/record/family/assertions-v1.d.ts +2 -0
  197. package/dist/record/family/assertions-v1.js +6 -0
  198. package/dist/record/family/assertions-v1.js.map +1 -0
  199. package/dist/record/family/assertions.cjs +16 -5
  200. package/dist/record/family/assertions.cjs.map +1 -1
  201. package/dist/record/family/assertions.d.cts +510 -56
  202. package/dist/record/family/assertions.d.mts +510 -56
  203. package/dist/record/family/assertions.d.ts +510 -56
  204. package/dist/record/family/assertions.js +14 -10
  205. package/dist/record/family/catalog.cjs +17 -1
  206. package/dist/record/family/catalog.cjs.map +1 -1
  207. package/dist/record/family/catalog.d.cts +1292 -104
  208. package/dist/record/family/catalog.d.mts +1292 -104
  209. package/dist/record/family/catalog.d.ts +1292 -104
  210. package/dist/record/family/catalog.js +22 -20
  211. package/dist/record/family/index.js +144 -138
  212. package/dist/record/family/observability-v1.cjs +5 -0
  213. package/dist/record/family/observability-v1.cjs.map +1 -1
  214. package/dist/record/family/observability.cjs +1 -1
  215. package/dist/record/family/observability.cjs.map +1 -1
  216. package/dist/record/host/runtime.cjs +323 -173
  217. package/dist/record/host/runtime.cjs.map +1 -1
  218. package/dist/record/host/types.cjs.map +1 -1
  219. package/dist/record/host/types.d.cts +9 -7
  220. package/dist/record/host/types.d.mts +9 -7
  221. package/dist/record/host/types.d.ts +9 -7
  222. package/dist/record/model/core.cjs.map +1 -1
  223. package/dist/record/model/core.d.cts +1 -2
  224. package/dist/record/model/core.d.mts +1 -2
  225. package/dist/record/model/core.d.ts +1 -2
  226. package/dist/record/model/definition.cjs +1 -2
  227. package/dist/record/model/definition.cjs.map +1 -1
  228. package/dist/record/model/definition.d.cts +1 -7
  229. package/dist/record/model/definition.d.mts +1 -7
  230. package/dist/record/model/definition.d.ts +1 -7
  231. package/dist/record/model/identifiers.cjs +2 -3
  232. package/dist/record/model/identifiers.cjs.map +1 -1
  233. package/dist/record/model/identifiers.d.cts +1 -3
  234. package/dist/record/model/identifiers.d.mts +1 -3
  235. package/dist/record/model/identifiers.d.ts +1 -3
  236. package/dist/record/model/identifiers.js +36 -38
  237. package/dist/record/model/index.js +60 -62
  238. package/dist/record/platform/node.cjs +17 -7
  239. package/dist/record/platform/node.cjs.map +1 -1
  240. package/dist/record/platform/services.cjs.map +1 -1
  241. package/dist/record/platform/services.d.cts +8 -3
  242. package/dist/record/platform/services.d.mts +8 -3
  243. package/dist/record/platform/services.d.ts +8 -3
  244. package/dist/record/reader/format.cjs +16 -65
  245. package/dist/record/reader/format.cjs.map +1 -1
  246. package/dist/record/reader/format.d.cts +5 -9
  247. package/dist/record/reader/format.d.mts +5 -9
  248. package/dist/record/reader/format.d.ts +5 -9
  249. package/dist/record/reader/index.js +10 -8
  250. package/dist/record/reader/runtime.cjs +63 -32
  251. package/dist/record/reader/runtime.cjs.map +1 -1
  252. package/dist/record/reader/runtime.d.cts +40 -4
  253. package/dist/record/reader/runtime.d.mts +40 -4
  254. package/dist/record/reader/runtime.d.ts +40 -4
  255. package/dist/record/reader/runtime.js +6 -4
  256. package/dist/report/assets/enhance.js +5 -5
  257. package/dist/report/assets/styles.css +190 -24
  258. package/dist/report/built-in/result-components.cjs +8 -5
  259. package/dist/report/built-in/result-components.cjs.map +1 -1
  260. package/dist/report/built-in/standard.cjs +3 -0
  261. package/dist/report/built-in/standard.cjs.map +1 -1
  262. package/dist/report/built-in/standard.d.cts +2 -0
  263. package/dist/report/built-in/standard.d.mts +2 -0
  264. package/dist/report/built-in/standard.d.ts +2 -0
  265. package/dist/report/client-dist/app.js +53 -0
  266. package/dist/report/components/attempt-detail/compute.cjs +108 -231
  267. package/dist/report/components/attempt-detail/compute.cjs.map +1 -1
  268. package/dist/report/components/attempt-detail/compute.d.cts +11 -45
  269. package/dist/report/components/attempt-detail/compute.d.mts +11 -45
  270. package/dist/report/components/attempt-detail/compute.d.ts +11 -45
  271. package/dist/report/components/attempt-detail/compute.js +2 -4
  272. package/dist/report/components/attempt-detail/content.cjs +4 -4
  273. package/dist/report/components/attempt-detail/content.cjs.map +1 -1
  274. package/dist/report/components/attempt-detail/validate.cjs +47 -4
  275. package/dist/report/components/attempt-detail/validate.cjs.map +1 -1
  276. package/dist/report/components/entity-lists/compute.cjs +9 -15
  277. package/dist/report/components/entity-lists/compute.cjs.map +1 -1
  278. package/dist/report/components/entity-lists/content.cjs +53 -40
  279. package/dist/report/components/entity-lists/content.cjs.map +1 -1
  280. package/dist/report/components/entity-lists/index.cjs +1 -3
  281. package/dist/report/components/entity-lists/index.cjs.map +1 -1
  282. package/dist/report/components/experiment-detail/compute.cjs +4 -6
  283. package/dist/report/components/experiment-detail/compute.cjs.map +1 -1
  284. package/dist/report/components/summaries/index.cjs +5 -1
  285. package/dist/report/components/summaries/index.cjs.map +1 -1
  286. package/dist/report/definition/cell.cjs +14 -7
  287. package/dist/report/definition/cell.cjs.map +1 -1
  288. package/dist/report/definition/cell.d.cts +4 -0
  289. package/dist/report/definition/cell.d.mts +4 -0
  290. package/dist/report/definition/cell.d.ts +4 -0
  291. package/dist/report/definition/primitives/assertion-evidence.cjs +96 -0
  292. package/dist/report/definition/primitives/assertion-evidence.cjs.map +1 -0
  293. package/dist/report/definition/primitives/assertion-evidence.d.cts +11 -0
  294. package/dist/report/definition/primitives/assertion-evidence.d.mts +11 -0
  295. package/dist/report/definition/primitives/assertion-evidence.d.ts +11 -0
  296. package/dist/report/definition/primitives/assertion-evidence.js +6 -0
  297. package/dist/report/definition/primitives/assertion-evidence.js.map +1 -0
  298. package/dist/report/definition/primitives/conversation.cjs +7 -0
  299. package/dist/report/definition/primitives/conversation.cjs.map +1 -1
  300. package/dist/report/definition/primitives/conversation.d.cts +2 -0
  301. package/dist/report/definition/primitives/conversation.d.mts +2 -0
  302. package/dist/report/definition/primitives/conversation.d.ts +2 -0
  303. package/dist/report/definition/primitives/turn-trace.cjs +53 -3
  304. package/dist/report/definition/primitives/turn-trace.cjs.map +1 -1
  305. package/dist/report/definition/primitives.cjs +14 -1
  306. package/dist/report/definition/primitives.cjs.map +1 -1
  307. package/dist/report/definition/report.cjs +16 -8
  308. package/dist/report/definition/report.cjs.map +1 -1
  309. package/dist/report/definition/report.d.cts +6 -0
  310. package/dist/report/definition/report.d.mts +6 -0
  311. package/dist/report/definition/report.d.ts +6 -0
  312. package/dist/report/definition/table-text.cjs +12 -3
  313. package/dist/report/definition/table-text.cjs.map +1 -1
  314. package/dist/report/definition/table-text.d.cts +6 -1
  315. package/dist/report/definition/table-text.d.mts +6 -1
  316. package/dist/report/definition/table-text.d.ts +6 -1
  317. package/dist/report/execution/model.cjs +6 -14
  318. package/dist/report/execution/model.cjs.map +1 -1
  319. package/dist/report/execution/model.d.cts +5 -10
  320. package/dist/report/execution/model.d.mts +5 -10
  321. package/dist/report/execution/model.d.ts +5 -10
  322. package/dist/report/execution/model.js +6 -6
  323. package/dist/report/execution/paths.cjs +0 -11
  324. package/dist/report/execution/paths.cjs.map +1 -1
  325. package/dist/report/execution/paths.d.cts +0 -1
  326. package/dist/report/execution/paths.d.mts +0 -1
  327. package/dist/report/execution/paths.d.ts +0 -1
  328. package/dist/report/execution/paths.js +6 -8
  329. package/dist/report/host/execute.cjs +13 -22
  330. package/dist/report/host/execute.cjs.map +1 -1
  331. package/dist/report/host/execute.d.cts +1 -4
  332. package/dist/report/host/execute.d.mts +1 -4
  333. package/dist/report/host/execute.d.ts +1 -4
  334. package/dist/report/host/from-record.cjs +3 -0
  335. package/dist/report/host/from-record.cjs.map +1 -1
  336. package/dist/report/host/from-record.d.cts +4 -1
  337. package/dist/report/host/from-record.d.mts +4 -1
  338. package/dist/report/host/from-record.d.ts +4 -1
  339. package/dist/report/host/site-assets.cjs +1 -2
  340. package/dist/report/host/site-assets.cjs.map +1 -1
  341. package/dist/report/host/site-assets.d.cts +0 -1
  342. package/dist/report/host/site-assets.d.mts +0 -1
  343. package/dist/report/host/site-assets.d.ts +0 -1
  344. package/dist/report/host/site-assets.js +2 -4
  345. package/dist/report/host/static.cjs +71 -346
  346. package/dist/report/host/static.cjs.map +1 -1
  347. package/dist/report/host/static.d.cts +1 -3
  348. package/dist/report/host/static.d.mts +1 -3
  349. package/dist/report/host/static.d.ts +1 -3
  350. package/dist/report/host/static.js +2 -2
  351. package/dist/report/host/view-session.cjs.map +1 -1
  352. package/dist/report/host/view-session.d.cts +1 -1
  353. package/dist/report/host/view-session.d.mts +1 -1
  354. package/dist/report/host/view-session.d.ts +1 -1
  355. package/dist/report/model/locale.cjs +1 -1
  356. package/dist/report/model/locale.cjs.map +1 -1
  357. package/dist/report/model/locale.d.cts +1 -1
  358. package/dist/report/model/locale.d.mts +1 -1
  359. package/dist/report/model/locale.d.ts +1 -1
  360. package/dist/report/model/pricing.cjs +2 -2
  361. package/dist/report/model/pricing.cjs.map +1 -1
  362. package/dist/report/react/enhance.js +5 -5
  363. package/dist/report/react/styles.css +190 -24
  364. package/dist/runner/adoption.cjs +4 -7
  365. package/dist/runner/adoption.cjs.map +1 -1
  366. package/dist/runner/attempt-resources.cjs +60 -0
  367. package/dist/runner/attempt-resources.cjs.map +1 -0
  368. package/dist/runner/attempt-resources.d.cts +13 -0
  369. package/dist/runner/attempt-resources.d.mts +13 -0
  370. package/dist/runner/attempt-resources.d.ts +13 -0
  371. package/dist/runner/attempt-resources.js +6 -0
  372. package/dist/runner/attempt-resources.js.map +1 -0
  373. package/dist/runner/attempt.cjs +37 -3
  374. package/dist/runner/attempt.cjs.map +1 -1
  375. package/dist/runner/feedback/failure.cjs +77 -2
  376. package/dist/runner/feedback/failure.cjs.map +1 -1
  377. package/dist/runner/feedback/failure.d.cts +2 -1
  378. package/dist/runner/feedback/failure.d.mts +2 -1
  379. package/dist/runner/feedback/failure.d.ts +2 -1
  380. package/dist/runner/project-current.cjs +2 -13
  381. package/dist/runner/project-current.cjs.map +1 -1
  382. package/dist/runner/project-current.d.cts +4 -2
  383. package/dist/runner/project-current.d.mts +4 -2
  384. package/dist/runner/project-current.d.ts +4 -2
  385. package/dist/runner/reporters/braintrust.cjs +7 -7
  386. package/dist/runner/reporters/braintrust.cjs.map +1 -1
  387. package/dist/runner/reuse-readback.cjs +3 -4
  388. package/dist/runner/reuse-readback.cjs.map +1 -1
  389. package/dist/runner/run.cjs +5 -2
  390. package/dist/runner/run.cjs.map +1 -1
  391. package/dist/sandbox/backend.cjs +32 -2
  392. package/dist/sandbox/backend.cjs.map +1 -1
  393. package/dist/sandbox/backend.d.cts +12 -1
  394. package/dist/sandbox/backend.d.mts +12 -1
  395. package/dist/sandbox/backend.d.ts +12 -1
  396. package/dist/sandbox/backend.js +28 -22
  397. package/dist/sandbox/docker.cjs +140 -0
  398. package/dist/sandbox/docker.cjs.map +1 -1
  399. package/dist/sandbox/docker.d.cts +3 -1
  400. package/dist/sandbox/docker.d.mts +3 -1
  401. package/dist/sandbox/docker.d.ts +3 -1
  402. package/dist/sandbox/e2b.cjs +42 -0
  403. package/dist/sandbox/e2b.cjs.map +1 -1
  404. package/dist/sandbox/e2b.d.cts +2 -0
  405. package/dist/sandbox/e2b.d.mts +2 -0
  406. package/dist/sandbox/e2b.d.ts +2 -0
  407. package/dist/sandbox/local.cjs +64 -0
  408. package/dist/sandbox/local.cjs.map +1 -1
  409. package/dist/sandbox/local.d.cts +3 -1
  410. package/dist/sandbox/local.d.mts +3 -1
  411. package/dist/sandbox/local.d.ts +3 -1
  412. package/dist/sandbox/managed-process.cjs +49 -0
  413. package/dist/sandbox/managed-process.cjs.map +1 -0
  414. package/dist/sandbox/managed-process.d.cts +11 -0
  415. package/dist/sandbox/managed-process.d.mts +11 -0
  416. package/dist/sandbox/managed-process.d.ts +11 -0
  417. package/dist/sandbox/managed-process.js +6 -0
  418. package/dist/sandbox/managed-process.js.map +1 -0
  419. package/dist/sandbox/types.cjs.map +1 -1
  420. package/dist/sandbox/types.d.cts +24 -0
  421. package/dist/sandbox/types.d.mts +24 -0
  422. package/dist/sandbox/types.d.ts +24 -0
  423. package/dist/sandbox/vercel.cjs +1 -0
  424. package/dist/sandbox/vercel.cjs.map +1 -1
  425. package/dist/sandbox/vercel.d.cts +1 -0
  426. package/dist/sandbox/vercel.d.mts +1 -0
  427. package/dist/sandbox/vercel.d.ts +1 -0
  428. package/dist/view/server.cjs +2 -37
  429. package/dist/view/server.cjs.map +1 -1
  430. package/dist/view/server.d.cts +0 -3
  431. package/dist/view/server.d.mts +0 -3
  432. package/dist/view/server.d.ts +0 -3
  433. package/docs-site/zh/reference/builtin-agents.mdx +7 -7
  434. package/docs-site/zh/reference/official-adapters.mdx +4 -4
  435. package/docs-site/zh/reference/report-components.mdx +2 -2
  436. package/docs-site/zh/tutorials/configuration.mdx +2 -2
  437. package/docs-site/zh/tutorials/deploy-report-site.mdx +24 -2
  438. package/docs-site/zh/tutorials/publish-report.mdx +4 -4
  439. package/docs-site/zh/tutorials/sandbox-agent.mdx +5 -5
  440. package/docs-site/zh/tutorials/theming.mdx +2 -2
  441. package/package.json +4 -6
  442. package/dist/record/migration/index.cjs +0 -5
  443. package/dist/record/migration/index.cjs.map +0 -1
  444. package/dist/record/migration/index.d.cts +0 -11
  445. package/dist/record/migration/index.d.mts +0 -11
  446. package/dist/record/migration/index.d.ts +0 -11
  447. package/dist/record/migration/index.js +0 -6
  448. package/dist/record/migration/index.js.map +0 -1
  449. package/dist/report/host/site-runtime.cjs +0 -92
  450. package/dist/report/host/site-runtime.cjs.map +0 -1
  451. package/dist/report/host/site-runtime.d.cts +0 -9
  452. package/dist/report/host/site-runtime.d.mts +0 -9
  453. package/dist/report/host/site-runtime.d.ts +0 -9
  454. package/dist/report/host/site-runtime.js +0 -10
  455. package/dist/report/host/site-runtime.js.map +0 -1
  456. package/dist/view/client-dist/app.css +0 -2
  457. package/dist/view/client-dist/app.js +0 -49
  458. package/dist/view/data.cjs +0 -3
  459. package/dist/view/data.cjs.map +0 -1
  460. package/dist/view/data.d.cts +0 -9
  461. package/dist/view/data.d.mts +0 -9
  462. package/dist/view/data.d.ts +0 -9
  463. package/dist/view/data.js +0 -4
  464. package/dist/view/data.js.map +0 -1
  465. package/dist/view/index.cjs +0 -24
  466. package/dist/view/index.cjs.map +0 -1
  467. package/dist/view/index.d.cts +0 -9
  468. package/dist/view/index.d.mts +0 -9
  469. package/dist/view/index.d.ts +0 -9
  470. package/dist/view/index.js +0 -28
  471. package/dist/view/index.js.map +0 -1
  472. package/dist/view/report-view-cache.cjs +0 -200
  473. package/dist/view/report-view-cache.cjs.map +0 -1
  474. package/dist/view/report-view-cache.d.cts +0 -67
  475. package/dist/view/report-view-cache.d.mts +0 -67
  476. package/dist/view/report-view-cache.d.ts +0 -67
  477. package/dist/view/report-view-cache.js +0 -8
  478. package/dist/view/report-view-cache.js.map +0 -1
  479. package/dist/view/shared/types.cjs +0 -3
  480. package/dist/view/shared/types.cjs.map +0 -1
  481. package/dist/view/shared/types.d.cts +0 -51
  482. package/dist/view/shared/types.d.mts +0 -51
  483. package/dist/view/shared/types.d.ts +0 -51
  484. package/dist/view/shared/types.js +0 -4
  485. package/dist/view/shared/types.js.map +0 -1
  486. package/dist/view/site.cjs +0 -32
  487. package/dist/view/site.cjs.map +0 -1
  488. package/dist/view/site.d.cts +0 -24
  489. package/dist/view/site.d.mts +0 -24
  490. package/dist/view/site.d.ts +0 -24
  491. package/dist/view/site.js +0 -8
  492. package/dist/view/site.js.map +0 -1
  493. package/dist/view/styles.css +0 -170
  494. package/dist/view/template.html +0 -17
@@ -0,0 +1,2807 @@
1
+ "use strict";
2
+ const __niceevalDynamicImport = (specifier) => import(specifier);
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.cliProgram = exports.EXPERIMENT_RENAME_SCHEMA_VERSION = exports.EXPERIMENT_RENAME_FORMAT = exports.FLAG_OPTIONS = exports.CliOperationError = exports.CliUsageError = void 0;
5
+ exports.renderCliFailure = renderCliFailure;
6
+ exports.foldInvocationEvalStats = foldInvocationEvalStats;
7
+ exports.parseExperimentRenamePositionals = parseExperimentRenamePositionals;
8
+ exports.firstExperimentRenameUnsupportedFlag = firstExperimentRenameUnsupportedFlag;
9
+ exports.renderExperimentRenamePlanHuman = renderExperimentRenamePlanHuman;
10
+ exports.renderExperimentRenameRejectedHuman = renderExperimentRenameRejectedHuman;
11
+ exports.renderExperimentRenameDoneHuman = renderExperimentRenameDoneHuman;
12
+ exports.renderExperimentRenameJson = renderExperimentRenameJson;
13
+ exports.experimentRenameRejectedFromError = experimentRenameRejectedFromError;
14
+ exports.experimentRenameExitCode = experimentRenameExitCode;
15
+ // niceeval CLI 入口。执行 eval 必须以 experiment 为单位;位置参数只在 exp 后筛 eval id 前缀。
16
+ // niceeval check [组|配置] [pattern] 只做发现、选择与 SandboxLayer pure link
17
+ // niceeval exp [组|配置] [pattern] 跑实验
18
+ // niceeval debug <配置> <eval> 只规划一个配对的 Sandbox / Plugin lifecycle
19
+ // niceeval accept @<locator>... 接受多条历史结果并重锚到当前配置
20
+ // niceeval show [selection] 终端渲染固定 Sample 的一个目标 Page
21
+ // niceeval list 只列出发现到的 eval
22
+ // niceeval clean [--record <root>] [--yes] 删除未完成 Run
23
+ // niceeval migrate [--record <root>] [--yes] 显式迁移 Record
24
+ const effect_1 = require("effect");
25
+ const application_ts_1 = require("./application.cjs");
26
+ const project_configuration_ts_1 = require("./project-configuration.cjs");
27
+ const invocationFacts = effect_1.Effect.flatMap(application_ts_1.CliInvocationFacts, (service) => service.facts).pipe(effect_1.Effect.mapError((cause) => cliFailure("read invocation facts", cause)));
28
+ const attempt_locator_ts_1 = require("../attempt-locator.cjs");
29
+ const aggregate_ts_1 = require("../shared/aggregate.cjs");
30
+ const index_ts_1 = require("../experiment/host/index.cjs");
31
+ const project_current_ts_1 = require("../runner/project-current.cjs");
32
+ const index_ts_2 = require("../record/index.cjs");
33
+ const identifiers_ts_1 = require("../record/codec/identifiers.cjs");
34
+ const attempt_overview_ts_1 = require("../report/built-in/attempt-overview.cjs");
35
+ const run_membership_overview_ts_1 = require("../report/built-in/run-membership-overview.cjs");
36
+ const standard_tsx_1 = require("../report/built-in/standard.cjs");
37
+ const execution_ts_1 = require("../report/built-in/execution.cjs");
38
+ const source_ts_1 = require("../report/built-in/source.cjs");
39
+ const from_record_ts_1 = require("../report/host/from-record.cjs");
40
+ const index_ts_3 = require("../report/host/index.cjs");
41
+ const panel_ts_1 = require("../report/model/panel.cjs");
42
+ const progress_ts_1 = require("../report/host/progress.cjs");
43
+ const theme_ts_1 = require("../report/theme.cjs");
44
+ const record_ts_1 = require("./record.cjs");
45
+ const eval_selection_ts_1 = require("../runner/eval-selection.cjs");
46
+ const registry_ts_1 = require("../sandbox/registry.cjs");
47
+ const cli_commands_ts_1 = require("../sandbox/cli-commands.cjs");
48
+ const link_ts_1 = require("../sandbox/link.cjs");
49
+ const plan_ts_1 = require("../sandbox/plan.cjs");
50
+ const experiment_cleanup_registry_ts_1 = require("../runner/experiment-cleanup-registry.cjs");
51
+ const lock_ts_1 = require("../runner/lock.cjs");
52
+ const shared_state_lease_ts_1 = require("../runner/shared-state-lease.cjs");
53
+ const cleanup_timeout_ts_1 = require("../runner/cleanup-timeout.cjs");
54
+ const timeout_ts_1 = require("../runner/timeout.cjs");
55
+ const teardown_registry_ts_1 = require("../runner/teardown-registry.cjs");
56
+ const rename_experiment_ts_1 = require("../runner/rename-experiment.cjs");
57
+ const verdict_ts_1 = require("../shared/verdict.cjs");
58
+ const sandbox_selection_ts_1 = require("../runner/sandbox-selection.cjs");
59
+ const json_ts_1 = require("../runner/reporters/json.cjs");
60
+ const index_ts_4 = require("../runner/feedback/index.cjs");
61
+ const index_ts_5 = require("../i18n/index.cjs");
62
+ const util_ts_1 = require("../util.cjs");
63
+ const session_ts_1 = require("../runner/session.cjs");
64
+ /** A recoverable command-line usage error. Defects deliberately do not enter this channel. */
65
+ class CliUsageError extends effect_1.Data.TaggedError("CliUsageError") {
66
+ }
67
+ exports.CliUsageError = CliUsageError;
68
+ /** A recoverable failure from a concrete CLI-owned boundary (file, process, or dynamic module). */
69
+ class CliOperationError extends effect_1.Data.TaggedError("CliOperationError") {
70
+ }
71
+ exports.CliOperationError = CliOperationError;
72
+ function usageError(message, exitCode = 1) {
73
+ return new CliUsageError({ message, exitCode });
74
+ }
75
+ function cliFailure(operation, cause, exitCode = 1) {
76
+ return cause instanceof CliUsageError || cause instanceof CliOperationError
77
+ ? cause
78
+ : new CliOperationError({ operation, cause, exitCode });
79
+ }
80
+ /**
81
+ * Lift one actual Promise boundary into the application Effect. It is never a
82
+ * private runtime: CLI owns the sole runtime in `cli/bootstrap.ts`.
83
+ */
84
+ function cliPromise(operation, promise) {
85
+ return effect_1.Effect.tryPromise({
86
+ try: promise,
87
+ catch: (cause) => cliFailure(operation, cause),
88
+ });
89
+ }
90
+ /** Preserve library effects without running a nested runtime. */
91
+ function cliEffect(operation, effect) {
92
+ return effect.pipe(effect_1.Effect.mapError((cause) => cliFailure(operation, cause)));
93
+ }
94
+ /** Bootstrap owns presentation of typed failures; defects and interruption stay in Cause. */
95
+ function renderCliFailure(failure) {
96
+ if (failure._tag === "CliUsageError")
97
+ return failure.message;
98
+ if (failure.cause instanceof link_ts_1.SandboxLayerLinkError)
99
+ return `${(0, link_ts_1.formatSandboxLayerLinkError)(failure.cause)}\n`;
100
+ if (failure.cause instanceof plan_ts_1.SandboxPhysicalPlanningError)
101
+ return `${(0, plan_ts_1.formatSandboxPhysicalPlanningError)(failure.cause)}\n`;
102
+ const assertionsMessage = runnerAssertionsMessage(failure.cause);
103
+ if (assertionsMessage !== undefined)
104
+ return `error: ${assertionsMessage}\n`;
105
+ if (isReportCliOperation(failure.operation)) {
106
+ const code = failureCode(failure.cause);
107
+ if (code !== undefined) {
108
+ const reason = stringProperty(failure.cause, "reason");
109
+ const operation = stringProperty(failure.cause, "operation");
110
+ if (reason !== undefined) {
111
+ const stage = operation === undefined ? "" : `${operation}: `;
112
+ const retry = code === "report-view-server-failed" && operation === "listen"
113
+ ? "\nChoose another port with --port <port>, or stop the process already listening there."
114
+ : "";
115
+ return `${code}: ${stage}${reason}${retry}\n`;
116
+ }
117
+ return `${code}\n`;
118
+ }
119
+ }
120
+ return (0, index_ts_5.t)("cli.error", { error: (0, util_ts_1.formatThrown)(failure.cause) });
121
+ }
122
+ function isReportCliOperation(operation) {
123
+ return operation === "load trusted Report config" ||
124
+ operation === "load trusted Report module" ||
125
+ operation === "load trusted Theme module" ||
126
+ operation === "execute report from Record" ||
127
+ operation === "render Report show output" ||
128
+ operation === "open report view session" ||
129
+ operation === "open report view" ||
130
+ operation === "export static Report";
131
+ }
132
+ function failureCode(value) {
133
+ if (typeof value !== "object" || value === null)
134
+ return undefined;
135
+ const code = Reflect.get(value, "code");
136
+ return typeof code === "string" ? code : undefined;
137
+ }
138
+ function runnerAssertionsMessage(value) {
139
+ if (failureCode(value) !== "runner-record-assertions-invalid" || typeof value !== "object" || value === null) {
140
+ return undefined;
141
+ }
142
+ const issue = Reflect.get(value, "issue");
143
+ if (typeof issue !== "object" || issue === null)
144
+ return undefined;
145
+ const message = Reflect.get(issue, "message");
146
+ return typeof message === "string" && message.length > 0 ? message : undefined;
147
+ }
148
+ // 表驱动的 flag 定义(node:util parseArgs)。--no-x 显式声明,不依赖 Node 20.14+
149
+ // 才支持的 allowNegative,也避免每个 boolean flag 都隐式获得未设计的负向别名。
150
+ // 未知 flag 由 strict 模式报清晰错误,不会静默吞掉后面的位置参数。
151
+ //
152
+ // 每个 flag 的 JSDoc 就是它在 docs-site/zh/reference/cli.mdx flag 表里的说明,由
153
+ // scripts/generate-reference.ts 提取渲染——改 flag 语义时改这里的注释即可,不用碰生成脚本。
154
+ // 负向 flag(no-x)与正向 flag 合并成一行展示,不需要单独写 JSDoc。
155
+ exports.FLAG_OPTIONS = {
156
+ /** experiment 运行不支持该 flag。要换 agent,请在 `experiments/` 下新增或复制一个配置文件。 */
157
+ agent: { type: "string" },
158
+ /** experiment 运行不支持该 flag。要换模型,请新增或复制一个 experiment 文件并修改 `model`。 */
159
+ model: { type: "string" },
160
+ /** 每个 eval 运行多少次,常用于 pass@N。 */
161
+ attempts: { type: "string" },
162
+ /** 设置同时运行的 eval 数量。 */
163
+ "max-concurrency": { type: "string" },
164
+ /** 设置同时进行的 Sandbox 镜像 lookup/build 数量;与 eval 并发独立,默认 2。 */
165
+ "max-build-concurrency": { type: "string" },
166
+ /** 单个 attempt 的超时时间,单位毫秒。解析链:`--timeout` > experiment > eval(`defineEval({ timeoutMs })`)> `niceeval.config.ts`,默认无上限(四层都没声明就不设 deadline);config 是缺省底而不是覆盖层,写了 config 不会让 eval 自己声明的上限失效。 */
167
+ timeout: { type: "string" },
168
+ /** 整次运行的预算上限(美元)。 */
169
+ budget: { type: "string" },
170
+ /** `exp` 命令专用:跑完留下 failed/errored attempt 的 Sandbox 现场(= `--keep-sandbox=failed`);`--keep-sandbox=all` 连 passed 也留。事后用 `niceeval sandbox list/enter/stop` 查看与销毁。 */
171
+ "keep-sandbox": { type: "boolean" },
172
+ /** `sandbox stop` 专用:销毁全部留存 Sandbox。 */
173
+ all: { type: "boolean" },
174
+ /** `sandbox diff` 专用:只看某个 send 窗口(如 `--window turn2` 或 `--window session2/turn1`);省略输出全部窗口的串联视图。 */
175
+ window: { type: "string" },
176
+ /** `sandbox diff` 专用:只看某个文件的 patch;省略输出该窗口的全部文件。 */
177
+ path: { type: "string" },
178
+ /** `sandbox enter` 专用:shell 退出后让现场保持运行,不送回休眠。 */
179
+ "leave-running": { type: "boolean" },
180
+ /** `sandbox list` 专用:核对强杀(`SIGKILL` / 断电)路径留下的无主沙箱实例(docker + e2b;vercel 无按元数据检索实例的通道,不参与)。只读,不清理;销毁走 `niceeval sandbox prune`。 */
181
+ orphans: { type: "boolean" },
182
+ /** 只运行带有该 tag 的 eval(见 `defineEval` 的 `tags`)。 */
183
+ tag: { type: "string" },
184
+ /** 额外写一份 JUnit XML 报告到指定路径,供 CI 消费。 */
185
+ junit: { type: "string" },
186
+ /** `exp` 运行在 stdout 输出单一有序的 NDJSON 事件流;`exp --dry` 与 `debug` 输出各自的单个 JSON 计划文档。`show` 输出 Host 拥有的内建或自定义单目标机器文档,不形成完整站点或打开第二条取数路径。 */
187
+ json: { type: "boolean" },
188
+ /** `docker profile doctor` 专用:启动受限 DinD 容器并运行内层容器。 */
189
+ smoke: { type: "boolean" },
190
+ /** `view` 命令专用:把结果查看器静态导出到指定目录。 */
191
+ out: { type: "string" },
192
+ /** `view` 命令专用:指定本地服务器监听端口。 */
193
+ port: { type: "string" },
194
+ /** `view` 命令专用:指定监听地址;省略时为 127.0.0.1,只写 `--host` 时为 0.0.0.0。非 loopback 监听无认证或 TLS,会向所有可达客户端暴露报告数据。 */
195
+ host: { type: "string" },
196
+ // 以下旧 show 切片只为实现收敛期间保留解析位置,不属于目标公开 CLI,也不进入参考页。
197
+ /** `show` 专用:按一个 exact Attempt locator 展示已记录的 source snapshot。 */
198
+ source: { type: "boolean" },
199
+ /** `show @<AttemptLocator> --execution`:从公开 Record projection 呈现该 Attempt 的 transcript、tool 与 command evidence。 */
200
+ execution: { type: "boolean" },
201
+ /** `show @<AttemptLocator> --timing[=summary|full]` 专用:从内建 timing Report 读取该 Attempt 的 runner 阶段树。 */
202
+ timing: { type: "boolean" },
203
+ /** `show @<AttemptId> --execution` 专用:JS 正则过滤 retained transcript、tool 与 command evidence。 */
204
+ grep: { type: "string" },
205
+ // --diff 是布尔;--diff=<路径> 在 parseArgs 前预扫成 diffPath(路径必须 = 连写,
206
+ // 空格形式的下一个 token 仍是位置参数 = eval id 前缀,与文档一致)。
207
+ /** 实现收敛占位;目标公开 CLI 通过 Report page 读取 diff 通道。 */
208
+ diff: { type: "boolean" },
209
+ /** 实现收敛占位;目标公开 CLI 不提供跨 Run history。 */
210
+ history: { type: "boolean" },
211
+ /** 实现收敛占位;目标公开 CLI 通过 Report Calculation 呈现用量。 */
212
+ usage: { type: "boolean" },
213
+ /** 实现收敛占位;目标公开 CLI 不提供隐式跨 Run 统计。 */
214
+ stats: { type: "boolean" },
215
+ /** `show` / `view` 命令专用:按完整 ExperimentId 收窄当前项目结果;可重复,不接受前缀或逗号列表。 */
216
+ experiment: { type: "string", multiple: true },
217
+ /** `show` / `view` / `accept` / `sandbox enter|list|stop` 共用:指定实际 Record root;CLI 不补接 `.niceeval/record` 或其它后缀。 */
218
+ record: { type: "string" },
219
+ /** `show` / `view` 可重复传入 `--run`;每次按完整 RunId 增加一个显式 Run,重复 identity 去重。 */
220
+ run: { type: "string", multiple: true },
221
+ /** `show` / `view` 命令专用:内建 `standard` 或受信任的 Report module 路径。 */
222
+ report: { type: "string" },
223
+ /** `show` / `view` 命令专用:内建 Theme 或受信任的闭合 Theme module 路径。 */
224
+ theme: { type: "string" },
225
+ /** `show` / `view` 命令专用:选择报告的初始页;`show` 渲染该页并在尾部附其余页索引,`view` 以它作初始路由。未命中的页 id 按用法错误退出并列出可用页 id。 */
226
+ page: { type: "string" },
227
+ /** `exp` 命令专用:补齐被强杀打断的实验级 teardown——只对选中的实验各执行一次 teardown(新进程语义),不派发 attempt、不跑 setup;没有遗留登记也照常执行。与 eval 前缀位置参数组合是用法错误。 */
228
+ teardown: { type: "boolean" },
229
+ /** `exp --teardown` 专用:显式恢复一个永不自动接管的 sharedState lease;必须同时提供 --owner-token、--confirm-owner-terminated 和 --confirm-remote-quiesced。 */
230
+ "recover-shared-state": { type: "string" },
231
+ /** `--recover-shared-state` 专用:要恢复的不可变 owner token;错误或过期 token 不会修改 lease。 */
232
+ "owner-token": { type: "string" },
233
+ /** `--recover-shared-state` 专用:确认原 owner 已终止。 */
234
+ "confirm-owner-terminated": { type: "boolean" },
235
+ /** `--recover-shared-state` 专用:确认远端外部状态已静止。 */
236
+ "confirm-remote-quiesced": { type: "boolean" },
237
+ /** 只打印本次会匹配到的 eval × 运行配置,不实际执行(人读文本或 `--json` 单文档,见「机器怎么读:--json」)。 */
238
+ dry: { type: "boolean" },
239
+ /** `sandbox prune` 专用:除 orphan 外也销毁 unverified 实例;`exp` 明确拒绝此 flag,重跑失败项或全部项请用 `--rerun` / `--rerun all`。 */
240
+ force: { type: "boolean" },
241
+ /** `exp` 命令专用:重新运行失败项(裸写/failed)或全部项(all),不改变长期指纹。 */
242
+ rerun: { type: "boolean" },
243
+ /** 某个 eval 的一次 attempt 通过后,停止该 eval 剩余的 attempts;省略默认关(`attempts` 默认跑满、测完整通过率)。 */
244
+ "early-exit": { type: "boolean" },
245
+ /** 强制关闭首过即停,即使实验文件里写了 `earlyExit: true`。 */
246
+ "no-early-exit": { type: "boolean" },
247
+ /** `view` 命令专用:启动后自动打开浏览器(默认行为)。 */
248
+ open: { type: "boolean" },
249
+ "no-open": { type: "boolean" },
250
+ /** `clean` / `migrate` 专用:确认不可逆 maintenance 动作或在 clean Git restore point 下执行 migration。 */
251
+ yes: { type: "boolean" },
252
+ /** 打印用法说明并退出。 */
253
+ help: { type: "boolean", short: "h" },
254
+ /** 打印 niceeval 的版本号并退出。 */
255
+ version: { type: "boolean", short: "v" },
256
+ };
257
+ function numberFlag(name, raw) {
258
+ if (raw === undefined)
259
+ return undefined;
260
+ const n = Number(raw);
261
+ if (!Number.isFinite(n)) {
262
+ throw usageError((0, index_ts_5.t)("cli.flag.invalidNumber", { flag: name, value: raw }));
263
+ }
264
+ return n;
265
+ }
266
+ const CLI_COMMANDS = ["check", "exp", "debug", "accept", "show", "list", "view", "clean", "migrate", "init", "run", "sandbox", "session", "docker"];
267
+ function isCliCommand(candidate) {
268
+ return CLI_COMMANDS.some((command) => command === candidate);
269
+ }
270
+ function parseArgs(argv, parseTokens) {
271
+ if (argv[0] === "--")
272
+ argv = argv.slice(1);
273
+ if (argv.some((arg) => arg === "--strict" || arg.startsWith("--strict="))) {
274
+ throw usageError((0, index_ts_5.t)("cli.flag.strictRemoved"));
275
+ }
276
+ // --diff=<路径> 预扫:diff 本体是布尔(裸 --diff = 文件级摘要),路径只接受 = 连写。
277
+ let diffPath;
278
+ // --keep-sandbox[=failed|all] 预扫:本体是布尔(裸 = failed 档),档位只接受 = 连写。
279
+ let keepSandboxTier;
280
+ // --timing[=summary|full] 预扫:node:util 的单个 option 不支持 boolean|string 联合,
281
+ // 所以 mode 在严格 parseArgs 前提取,再把两种形式统一成布尔 --timing。
282
+ let timingMode;
283
+ // --source[=full|<path>] 预扫:本体是布尔,值只接受 = 连写,避免吞掉 eval 前缀位置参数。
284
+ let sourceValue;
285
+ let rerunMode;
286
+ // boolean|string 联合 flag 的空格写法先归一:--rerun all → --rerun=all;
287
+ // --host 的裸写则补成默认地址,保持 `--host <地址>` 的普通 string flag 形态。
288
+ {
289
+ const normalized = [];
290
+ for (let index = 0; index < argv.length; index += 1) {
291
+ const arg = argv[index];
292
+ const next = argv[index + 1];
293
+ if (arg === "--rerun" && (next === "failed" || next === "all")) {
294
+ normalized.push(`--rerun=${next}`);
295
+ index += 1;
296
+ }
297
+ else if (arg === "--host" && (next === undefined || next.startsWith("-"))) {
298
+ normalized.push("--host=0.0.0.0");
299
+ }
300
+ else {
301
+ normalized.push(arg);
302
+ }
303
+ }
304
+ argv = normalized;
305
+ }
306
+ argv = argv.map((arg) => {
307
+ if (arg.startsWith("--host=")) {
308
+ const value = arg.slice("--host=".length);
309
+ if (value.length === 0) {
310
+ throw usageError("--host=<address> requires a non-empty address, or use bare --host.\n");
311
+ }
312
+ }
313
+ if (arg.startsWith("--source=")) {
314
+ const value = arg.slice("--source=".length);
315
+ if (value.length === 0) {
316
+ throw usageError("--source=<path> requires a non-empty captured source path, or use bare --source.\n");
317
+ }
318
+ sourceValue = value;
319
+ return "--source";
320
+ }
321
+ if (arg.startsWith("--diff=")) {
322
+ const path = arg.slice("--diff=".length);
323
+ if (path)
324
+ diffPath = path;
325
+ return "--diff";
326
+ }
327
+ if (arg.startsWith("--keep-sandbox=")) {
328
+ const tier = arg.slice("--keep-sandbox=".length);
329
+ if (tier !== "failed" && tier !== "all") {
330
+ throw usageError(`--keep-sandbox only accepts "failed" (default) or "all", got "${tier}".\n`);
331
+ }
332
+ keepSandboxTier = tier;
333
+ return "--keep-sandbox";
334
+ }
335
+ if (arg.startsWith("--timing=")) {
336
+ const mode = arg.slice("--timing=".length);
337
+ if (mode !== "summary" && mode !== "full") {
338
+ throw usageError(`--timing only accepts "summary" (default) or "full", got "${mode}".\n`);
339
+ }
340
+ timingMode = mode;
341
+ return "--timing";
342
+ }
343
+ if (arg.startsWith("--rerun=")) {
344
+ const mode = arg.slice("--rerun=".length);
345
+ if (mode !== "failed" && mode !== "all") {
346
+ throw usageError(`--rerun only accepts "failed" (default) or "all", got "${mode}".\n`);
347
+ }
348
+ rerunMode = mode;
349
+ return "--rerun";
350
+ }
351
+ // `--output` 整个删除(见 docs/feature/experiments/cli.md 与 memory/exp-output-two-forms-ruling.md):
352
+ // beta 不留别名,任何取值(裸 flag 或 `--output=value`)都按用法错误拒绝,不静默吞掉、也不
353
+ // 落到 node:util parseArgs 的通用「unknown option」文案——给出专门的 error:/fix: 两行,
354
+ // 指向唯一还存在的两条路径:不加 flag 跑人读文本,机器面用 `--json`。
355
+ if (arg === "--output" || arg.startsWith("--output=")) {
356
+ throw usageError((0, index_ts_5.t)("cli.flag.outputRemoved"));
357
+ }
358
+ return arg;
359
+ });
360
+ let values;
361
+ let rawPositionals;
362
+ let providedOptions;
363
+ try {
364
+ const parsed = parseTokens(argv, exports.FLAG_OPTIONS);
365
+ values = parsed.values;
366
+ rawPositionals = parsed.positionals;
367
+ providedOptions = parsed.tokens
368
+ .filter((token) => token.kind === "option" && token.name !== undefined)
369
+ .map((token) => token.name);
370
+ }
371
+ catch (e) {
372
+ if (e instanceof CliUsageError)
373
+ throw e;
374
+ throw usageError((0, index_ts_5.t)("cli.flag.parseError", { message: e instanceof Error ? e.message : String(e) }));
375
+ }
376
+ // 第一个位置参数必须是已知命令;其余是 eval id 前缀 / view 输入。
377
+ // 裸 eval id 早已不再是运行入口,所以不识别的首 token 应当就地报用法错误,
378
+ // 不应先装载项目 config / eval 再偶然以其它错误退出。
379
+ let command = "run";
380
+ let positionals = rawPositionals;
381
+ if (rawPositionals.length > 0) {
382
+ const candidate = rawPositionals[0];
383
+ if (!isCliCommand(candidate)) {
384
+ throw usageError((0, index_ts_5.t)("cli.command.unknown", { command: candidate }));
385
+ }
386
+ command = candidate;
387
+ positionals = rawPositionals.slice(1);
388
+ }
389
+ const flags = {
390
+ agent: values.agent,
391
+ model: values.model,
392
+ attempts: numberFlag("attempts", values.attempts),
393
+ maxConcurrency: numberFlag("max-concurrency", values["max-concurrency"]),
394
+ maxBuildConcurrency: numberFlag("max-build-concurrency", values["max-build-concurrency"]),
395
+ timeout: numberFlag("timeout", values.timeout),
396
+ budget: numberFlag("budget", values.budget),
397
+ tag: values.tag,
398
+ junit: values.junit,
399
+ json: values.json === true,
400
+ smoke: values.smoke === true,
401
+ out: values.out,
402
+ port: numberFlag("port", values.port),
403
+ host: values.host,
404
+ dry: values.dry === true,
405
+ force: values.force === true,
406
+ rerun: values.rerun === true ? (rerunMode ?? "failed") : undefined,
407
+ earlyExit: values["no-early-exit"] === true ? false : values["early-exit"] === true ? true : undefined,
408
+ open: values["no-open"] === true ? false : values.open === true ? true : undefined,
409
+ help: values.help === true,
410
+ version: values.version === true,
411
+ yes: values.yes === true,
412
+ source: values.source === true ? (sourceValue ?? true) : undefined,
413
+ execution: values.execution === true,
414
+ diff: values.diff === true && diffPath === undefined,
415
+ diffPath,
416
+ timing: values.timing === true ? (timingMode ?? "summary") : undefined,
417
+ grep: values.grep,
418
+ keepSandbox: values["keep-sandbox"] === true ? (keepSandboxTier ?? "failed") : undefined,
419
+ all: values.all === true,
420
+ window: values.window,
421
+ sandboxPath: values.path,
422
+ leaveRunning: values["leave-running"] === true,
423
+ history: values.history === true,
424
+ usage: values.usage === true,
425
+ stats: values.stats === true,
426
+ experiment: values.experiment,
427
+ record: values.record,
428
+ run: values.run,
429
+ report: values.report,
430
+ page: values.page,
431
+ theme: values.theme,
432
+ orphans: values.orphans === true,
433
+ teardown: values.teardown === true,
434
+ recoverSharedState: values["recover-shared-state"],
435
+ ownerToken: values["owner-token"],
436
+ confirmOwnerTerminated: values["confirm-owner-terminated"] === true,
437
+ confirmRemoteQuiesced: values["confirm-remote-quiesced"] === true,
438
+ };
439
+ return { command, positionals, flags, providedOptions };
440
+ }
441
+ function projectCurrentDryPlan(input) {
442
+ return Object.freeze({
443
+ slots: Object.freeze(input.slots.map(projectCurrentDryPlanSlot)),
444
+ readbacks: Object.freeze([...input.readbacks]),
445
+ });
446
+ }
447
+ function projectCurrentDryPlanSlot(slot) {
448
+ return Object.freeze({
449
+ runId: String(slot.runId),
450
+ slotId: String(slot.slotId),
451
+ experimentId: slot.experimentId,
452
+ evalId: slot.evalId,
453
+ attempt: slot.attempt,
454
+ state: slot.state === "reuse" ? "reused" : "gap",
455
+ comparisons: Object.freeze(slot.comparisons.map((comparison) => Object.freeze({
456
+ attachment: comparison.attachment,
457
+ recordedClaim: comparison.recordedClaim,
458
+ sourceState: comparison.sourceState,
459
+ result: comparison.result,
460
+ reason: comparison.reason,
461
+ }))),
462
+ ...(slot.state === "gap" ? { reason: slot.reason, scope: slot.scope } : {}),
463
+ });
464
+ }
465
+ function currentDryPlanRows(plan) {
466
+ const rows = new Map();
467
+ const rowFor = (experimentId, evalId) => {
468
+ const key = JSON.stringify([experimentId, evalId]);
469
+ const existing = rows.get(key);
470
+ if (existing !== undefined)
471
+ return existing;
472
+ const created = { experimentId, evalId, slots: [], readbacks: [] };
473
+ rows.set(key, created);
474
+ return created;
475
+ };
476
+ for (const slot of plan.slots)
477
+ rowFor(slot.experimentId, slot.evalId).slots.push(slot);
478
+ for (const readback of plan.readbacks) {
479
+ rowFor(readback.target.experimentId, readback.target.evalId).readbacks.push(readback);
480
+ }
481
+ return Object.freeze([...rows.values()].map((row) => Object.freeze({
482
+ experimentId: row.experimentId,
483
+ evalId: row.evalId,
484
+ slots: Object.freeze([...row.slots].sort((left, right) => left.attempt - right.attempt)),
485
+ readbacks: Object.freeze([...row.readbacks].sort((left, right) => left.target.attempt - right.target.attempt)),
486
+ })).sort((left, right) => left.experimentId.localeCompare(right.experimentId) || left.evalId.localeCompare(right.evalId)));
487
+ }
488
+ function renderCurrentDryPlan(input) {
489
+ const reused = input.rows.reduce((total, row) => total + row.slots.filter((slot) => slot.state === "reused").length, 0);
490
+ const lines = [
491
+ `plan: ${input.totalAttempts} attempts · ${input.evals} evals × ${input.configs} configs · runs ${input.attempts}`,
492
+ ...(reused === 0 ? [] : [`reuse: ${reused}/${input.totalAttempts} exact current Record attempts`]),
493
+ `plugins: ${input.pluginAudit.occurrences.length} lifecycle occurrences`,
494
+ ];
495
+ for (const row of input.rows) {
496
+ const reusedAttempts = row.slots.filter((slot) => slot.state === "reused").map((slot) => slot.attempt);
497
+ const gaps = row.slots.filter((slot) => slot.state === "gap");
498
+ const hasIdentityGap = gaps.some((slot) => slot.reason === "identity-mismatch");
499
+ const parts = [
500
+ ...(row.locked ? ["locked"] : []),
501
+ ...(reusedAttempts.length === 0 ? [] : [`reused ${reusedAttempts.join(",")}`]),
502
+ ...gaps.map((slot) => `gap ${slot.attempt}:${slot.reason ?? "unknown"}`),
503
+ ];
504
+ lines.push(`${row.experimentId} ${row.evalId} ${parts.join(" · ") || "no slots"}`);
505
+ for (const readback of row.readbacks) {
506
+ const verdict = readback.state === "reused"
507
+ ? readback.verdict
508
+ : readback.verdict.state === "available"
509
+ ? readback.verdict.value
510
+ : readback.verdict.state;
511
+ const locator = readback.source.locator;
512
+ lines.push(` source ${locator} · ${readback.state} · verdict ${verdict}`);
513
+ if (hasIdentityGap && readback.state === "prior") {
514
+ lines.push(` accept: niceeval accept ${locator}`);
515
+ }
516
+ }
517
+ }
518
+ return `${lines.join("\n")}\n`;
519
+ }
520
+ function renderCurrentDryPlanJson(input) {
521
+ const reused = input.rows.reduce((total, row) => total + row.slots.filter((slot) => slot.state === "reused").length, 0);
522
+ return `${JSON.stringify({
523
+ format: "niceeval.current-reuse-plan/v1",
524
+ schemaVersion: 1,
525
+ total: input.totalAttempts,
526
+ evals: input.evals,
527
+ configs: input.configs,
528
+ attempts: input.attempts,
529
+ reused,
530
+ matrix: input.rows,
531
+ plugins: input.pluginAudit.occurrences,
532
+ })}\n`;
533
+ }
534
+ /**
535
+ * Fold the invocation's real fresh results and current Record readbacks at the
536
+ * same eval identity. This intentionally projects only identity + terminal
537
+ * verdict; a reused readback is never recreated as an EvalResult.
538
+ */
539
+ function foldInvocationEvalStats(summary) {
540
+ const terminals = [
541
+ ...summary.results.map((result) => Object.freeze({
542
+ identity: `${result.experimentId ?? ""}|${result.id}`,
543
+ verdict: result.verdict,
544
+ })),
545
+ ...summary.reusedAttempts.map((readback) => Object.freeze({
546
+ identity: `${readback.target.experimentId}|${readback.target.evalId}`,
547
+ verdict: readback.source.evaluationKind !== "score"
548
+ ? readback.verdict
549
+ : readback.score.state === "applicable"
550
+ && readback.score.attachment.state === "available"
551
+ && readback.score.attachment.value.state === "complete"
552
+ ? "passed"
553
+ : "errored",
554
+ })),
555
+ ];
556
+ return (0, verdict_ts_1.evalLevelStats)(terminals, (terminal) => terminal.identity);
557
+ }
558
+ function firstViewerOnlyFlag(flags) {
559
+ const SHOW = "show";
560
+ const BOTH = "show / view";
561
+ const VIEW = "view";
562
+ if (flags.source)
563
+ return { flag: "--source", command: SHOW };
564
+ if (flags.execution)
565
+ return { flag: "--execution", command: SHOW };
566
+ if (flags.timing !== undefined)
567
+ return { flag: "--timing", command: SHOW };
568
+ if (flags.grep !== undefined)
569
+ return { flag: "--grep", command: SHOW };
570
+ if (flags.diff || flags.diffPath !== undefined)
571
+ return { flag: "--diff", command: SHOW };
572
+ if (flags.history)
573
+ return { flag: "--history", command: SHOW };
574
+ if (flags.usage)
575
+ return { flag: "--usage", command: SHOW };
576
+ if (flags.stats)
577
+ return { flag: "--stats", command: SHOW };
578
+ if (flags.experiment !== undefined)
579
+ return { flag: "--experiment", command: BOTH };
580
+ if (flags.report !== undefined)
581
+ return { flag: "--report", command: BOTH };
582
+ if (flags.theme !== undefined)
583
+ return { flag: "--theme", command: VIEW };
584
+ if (flags.page !== undefined)
585
+ return { flag: "--page", command: BOTH };
586
+ if (flags.run !== undefined)
587
+ return { flag: "--run", command: VIEW };
588
+ if (flags.out !== undefined)
589
+ return { flag: "--out", command: VIEW };
590
+ if (flags.port !== undefined)
591
+ return { flag: "--port", command: VIEW };
592
+ if (flags.open !== undefined)
593
+ return { flag: "--open", command: VIEW };
594
+ return undefined;
595
+ }
596
+ /** `clean` and `migrate` operate on a current Record root with explicit confirmation. */
597
+ function firstRecordMaintenanceUnsupportedFlag(flags) {
598
+ const unsupported = [
599
+ ["--agent", flags.agent],
600
+ ["--model", flags.model],
601
+ ["--attempts", flags.attempts],
602
+ ["--max-concurrency", flags.maxConcurrency],
603
+ ["--max-build-concurrency", flags.maxBuildConcurrency],
604
+ ["--timeout", flags.timeout],
605
+ ["--budget", flags.budget],
606
+ ["--tag", flags.tag],
607
+ ["--junit", flags.junit],
608
+ ["--json", flags.json],
609
+ ["--smoke", flags.smoke],
610
+ ["--dry", flags.dry],
611
+ ["--force", flags.force],
612
+ ["--rerun", flags.rerun],
613
+ ["--early-exit/--no-early-exit", flags.earlyExit],
614
+ ["--open/--no-open", flags.open],
615
+ ["--out", flags.out],
616
+ ["--port", flags.port],
617
+ ["--host", flags.host],
618
+ ["--source", flags.source],
619
+ ["--execution", flags.execution],
620
+ ["--diff", flags.diff || flags.diffPath !== undefined],
621
+ ["--grep", flags.grep],
622
+ ["--timing", flags.timing],
623
+ ["--keep-sandbox", flags.keepSandbox],
624
+ ["--all", flags.all],
625
+ ["--window", flags.window],
626
+ ["--path", flags.sandboxPath],
627
+ ["--leave-running", flags.leaveRunning],
628
+ ["--history", flags.history],
629
+ ["--usage", flags.usage],
630
+ ["--stats", flags.stats],
631
+ ["--experiment", flags.experiment],
632
+ ["--run", flags.run],
633
+ ["--report", flags.report],
634
+ ["--page", flags.page],
635
+ ["--theme", flags.theme],
636
+ ["--orphans", flags.orphans],
637
+ ["--teardown", flags.teardown],
638
+ ["--recover-shared-state", flags.recoverSharedState],
639
+ ["--owner-token", flags.ownerToken],
640
+ ["--confirm-owner-terminated", flags.confirmOwnerTerminated],
641
+ ["--confirm-remote-quiesced", flags.confirmRemoteQuiesced],
642
+ ];
643
+ const bad = unsupported.find(([flag, value]) => {
644
+ if (flag === "--open/--no-open" || flag === "--early-exit/--no-early-exit") {
645
+ return value !== undefined;
646
+ }
647
+ return value !== undefined && value !== false && (!Array.isArray(value) || value.length > 0);
648
+ });
649
+ return bad?.[0];
650
+ }
651
+ /**
652
+ * `accept` 接受一个或多个精确 locator;它不是 `exp` 的另一种选择器,也不派发 attempt。
653
+ * `--record` 是唯一允许的附加 flag,用来接受发布根或其它显式记录根里的结果。
654
+ */
655
+ function parseAcceptLocators(positionals, flags) {
656
+ if (positionals.length === 0 || positionals.some((locator) => !/^@[^@\s]+$/.test(locator))) {
657
+ throw usageError((0, index_ts_5.t)("cli.accept.usage"));
658
+ }
659
+ if (new Set(positionals).size !== positionals.length) {
660
+ throw usageError("niceeval accept rejects duplicate locators.\n");
661
+ }
662
+ // `parseArgs` 已经负责未知 flag;这里拒绝那些虽为全局已知、但会改变运行/查看语义的 flag。
663
+ // `--record` 是 accept 的唯一范围输入,help/version 在 main() 更早处理。
664
+ const unsupported = [
665
+ ["--agent", flags.agent],
666
+ ["--model", flags.model],
667
+ ["--attempts", flags.attempts],
668
+ ["--max-concurrency", flags.maxConcurrency],
669
+ ["--max-build-concurrency", flags.maxBuildConcurrency],
670
+ ["--timeout", flags.timeout],
671
+ ["--budget", flags.budget],
672
+ ["--tag", flags.tag],
673
+ ["--junit", flags.junit],
674
+ ["--json", flags.json],
675
+ ["--dry", flags.dry],
676
+ ["--force", flags.force],
677
+ ["--rerun", flags.rerun],
678
+ ["--early-exit/--no-early-exit", flags.earlyExit],
679
+ ["--open/--no-open", flags.open],
680
+ ["--source", flags.source],
681
+ ["--execution", flags.execution],
682
+ ["--diff", flags.diff || flags.diffPath !== undefined],
683
+ ["--grep", flags.grep],
684
+ ["--timing", flags.timing],
685
+ ["--keep-sandbox", flags.keepSandbox],
686
+ ["--all", flags.all],
687
+ ["--window", flags.window],
688
+ ["--path", flags.sandboxPath],
689
+ ["--leave-running", flags.leaveRunning],
690
+ ["--history", flags.history],
691
+ ["--usage", flags.usage],
692
+ ["--stats", flags.stats],
693
+ ["--experiment", flags.experiment],
694
+ ["--run", flags.run],
695
+ ["--report", flags.report],
696
+ ["--page", flags.page],
697
+ ["--theme", flags.theme],
698
+ ["--orphans", flags.orphans],
699
+ ["--teardown", flags.teardown],
700
+ ["--recover-shared-state", flags.recoverSharedState],
701
+ ["--owner-token", flags.ownerToken],
702
+ ["--confirm-owner-terminated", flags.confirmOwnerTerminated],
703
+ ["--confirm-remote-quiesced", flags.confirmRemoteQuiesced],
704
+ ];
705
+ const bad = unsupported.find(([flag, value]) => {
706
+ // `--no-open` / `--no-early-exit` are represented as false, but are still
707
+ // explicit flags and therefore invalid on this command.
708
+ if (flag === "--open/--no-open" || flag === "--early-exit/--no-early-exit")
709
+ return value !== undefined;
710
+ return value !== undefined && value !== false && (!Array.isArray(value) || value.length > 0);
711
+ });
712
+ if (bad) {
713
+ throw usageError((0, index_ts_5.t)("cli.accept.flagUnsupported", { flag: bad[0] }));
714
+ }
715
+ return [...positionals];
716
+ }
717
+ /** 调用 acceptance core;CLI 只负责 cwd/记录根边界、输出与退出码,不重建结果或启动 runner。 */
718
+ function runAcceptCommand(cwd, locators, recordRoot) {
719
+ return effect_1.Effect.gen(function* () {
720
+ const config = yield* loadConfig(cwd);
721
+ const results = yield* cliEffect("accept locators", index_ts_1.experimentHost.accept({
722
+ cwd,
723
+ config,
724
+ locators,
725
+ ...(recordRoot !== undefined ? { recordRoot } : {}),
726
+ }));
727
+ for (const result of results) {
728
+ yield* writeStdout((0, index_ts_5.t)("cli.accept.done", {
729
+ runId: result.runId,
730
+ sourceLocator: result.sourceLocator,
731
+ locator: result.locator,
732
+ fingerprint: result.fingerprint ?? "—",
733
+ }));
734
+ }
735
+ });
736
+ }
737
+ // ── exp rename:CLI 契约与纯解析/格式化 ──────────────────────────────────────
738
+ // 核心在 src/runner/rename-experiment.ts(planExperimentRename / renameExperiment,
739
+ // 资格门与写入都在那里,见 docs/source-map.md 与 docs/feature/experiments/rename.md);
740
+ // CLI 只做:解析 `exp rename <oldId> <newId>`、递选项进核心、把核心返回的计划/结果按
741
+ // 形态渲染。稳定 reason、计划与结果文档都是核心导出的契约,CLI 不在渲染层重建资格算法。
742
+ exports.EXPERIMENT_RENAME_FORMAT = "niceeval.experimentRename";
743
+ exports.EXPERIMENT_RENAME_SCHEMA_VERSION = 1;
744
+ const EXPERIMENT_RENAME_REASON_MESSAGE = {
745
+ "source-empty": "cli.rename.error.sourceEmpty",
746
+ "target-not-found": "cli.rename.error.targetNotFound",
747
+ "target-has-results": "cli.rename.error.targetHasResults",
748
+ "source-unreadable": "cli.rename.error.sourceUnreadable",
749
+ "artifact-unavailable": "cli.rename.error.artifactUnavailable",
750
+ "nothing-to-migrate": "cli.rename.error.nothingToMigrate",
751
+ };
752
+ /**
753
+ * 解析 exp rename 的位置参数:必须恰好两个(一个旧 id、一个新 id)。
754
+ * 纯函数,CLI 与测试共用。
755
+ */
756
+ function parseExperimentRenamePositionals(args) {
757
+ if (args.length !== 2 || args[0] === undefined || args[1] === undefined) {
758
+ return { ok: false, kind: "usage" };
759
+ }
760
+ return { ok: true, oldId: args[0], newId: args[1] };
761
+ }
762
+ /**
763
+ * exp rename 只允许 --dry / --json;返回第一个被误用的 flag,没有误用返回 undefined。
764
+ * 与 accept 同一套纪律:node:util parseArgs 已经报未知 flag,这里拒绝那些全局已知、
765
+ * 但会改变运行/查看语义的 flag。
766
+ */
767
+ function firstExperimentRenameUnsupportedFlag(flags) {
768
+ const unsupported = [
769
+ ["--agent", flags.agent],
770
+ ["--model", flags.model],
771
+ ["--attempts", flags.attempts],
772
+ ["--max-concurrency", flags.maxConcurrency],
773
+ ["--max-build-concurrency", flags.maxBuildConcurrency],
774
+ ["--timeout", flags.timeout],
775
+ ["--budget", flags.budget],
776
+ ["--tag", flags.tag],
777
+ ["--junit", flags.junit],
778
+ ["--force", flags.force],
779
+ ["--rerun", flags.rerun],
780
+ ["--early-exit/--no-early-exit", flags.earlyExit],
781
+ ["--open/--no-open", flags.open],
782
+ ["--source", flags.source],
783
+ ["--execution", flags.execution],
784
+ ["--diff", flags.diff || flags.diffPath !== undefined],
785
+ ["--grep", flags.grep],
786
+ ["--timing", flags.timing],
787
+ ["--keep-sandbox", flags.keepSandbox],
788
+ ["--all", flags.all],
789
+ ["--window", flags.window],
790
+ ["--path", flags.sandboxPath],
791
+ ["--leave-running", flags.leaveRunning],
792
+ ["--history", flags.history],
793
+ ["--usage", flags.usage],
794
+ ["--stats", flags.stats],
795
+ ["--experiment", flags.experiment],
796
+ ["--record", flags.record],
797
+ ["--run", flags.run],
798
+ ["--report", flags.report],
799
+ ["--page", flags.page],
800
+ ["--theme", flags.theme],
801
+ ["--orphans", flags.orphans],
802
+ ["--teardown", flags.teardown],
803
+ ["--recover-shared-state", flags.recoverSharedState],
804
+ ["--owner-token", flags.ownerToken],
805
+ ["--confirm-owner-terminated", flags.confirmOwnerTerminated],
806
+ ["--confirm-remote-quiesced", flags.confirmRemoteQuiesced],
807
+ ["--out", flags.out],
808
+ ["--port", flags.port],
809
+ ["--host", flags.host],
810
+ ];
811
+ const bad = unsupported.find(([flag, value]) => {
812
+ // `--no-open` / `--no-early-exit` 表现为 false,但仍是显式 flag,在 rename 上同样非法。
813
+ if (flag === "--open/--no-open" || flag === "--early-exit/--no-early-exit")
814
+ return value !== undefined;
815
+ return value !== undefined && value !== false && (!Array.isArray(value) || value.length > 0);
816
+ });
817
+ return bad?.[0];
818
+ }
819
+ /** 人读面:核心计划 → 预览文本。逐条列出迁移与排除;有 `blocked` 时点名阻断原因。 */
820
+ function renderExperimentRenamePlanHuman(plan) {
821
+ const lines = [(0, index_ts_5.t)("cli.rename.previewHeader", { oldId: plan.oldId, newId: plan.newId })];
822
+ if (plan.blocked !== undefined) {
823
+ lines.push((0, index_ts_5.t)("cli.rename.blocked", { reason: plan.blocked.reason }));
824
+ lines.push(...renderExperimentRenameBlockedDetail(plan.blocked));
825
+ }
826
+ if (plan.migrations.length > 0) {
827
+ lines.push((0, index_ts_5.t)("cli.rename.migratingHeader", { count: plan.migrations.length }));
828
+ for (const entry of plan.migrations) {
829
+ lines.push((0, index_ts_5.t)("cli.rename.migratingRow", {
830
+ evalId: entry.evalId,
831
+ sourceLocator: entry.sourceLocator,
832
+ newId: plan.newId,
833
+ }));
834
+ }
835
+ }
836
+ if (plan.excluded.length > 0) {
837
+ lines.push((0, index_ts_5.t)("cli.rename.excludedHeader", { count: plan.excluded.length }));
838
+ for (const entry of plan.excluded) {
839
+ lines.push((0, index_ts_5.t)("cli.rename.excludedRow", { evalId: entry.evalId, reason: entry.reason }));
840
+ }
841
+ }
842
+ return `${lines.join("\n")}\n`;
843
+ }
844
+ /** 拒绝的人读面:先给 reason 专属文案(点名旧 id、新 id、受影响 eval 与下一步),再补冲突清单。 */
845
+ function renderExperimentRenameRejectedHuman(rejected) {
846
+ const lines = [
847
+ (0, index_ts_5.t)(EXPERIMENT_RENAME_REASON_MESSAGE[rejected.reason], {
848
+ oldId: rejected.oldId,
849
+ newId: rejected.newId,
850
+ evalId: rejected.evalId ?? "",
851
+ }),
852
+ ];
853
+ lines.push(...renderExperimentRenameRejectedDetail(rejected));
854
+ return `${lines.join("\n")}\n`;
855
+ }
856
+ /** 成功的人读面:新 snapshot 路径 + 逐条新 locator。 */
857
+ function renderExperimentRenameDoneHuman(done) {
858
+ const lines = [
859
+ (0, index_ts_5.t)("cli.rename.doneHeader", { oldId: done.oldId, newId: done.newId, count: done.migrated.length }),
860
+ (0, index_ts_5.t)("cli.rename.snapshotPath", { path: done.snapshotPath }),
861
+ ];
862
+ for (const entry of done.migrated) {
863
+ lines.push((0, index_ts_5.t)("cli.rename.doneRow", {
864
+ evalId: entry.evalId,
865
+ sourceLocator: entry.sourceLocator,
866
+ locator: entry.locator,
867
+ }));
868
+ }
869
+ return `${lines.join("\n")}\n`;
870
+ }
871
+ /** 阻断的逐条事实:冲突 eval 逐行列出,并保留底层读取详情。 */
872
+ function renderExperimentRenameBlockedDetail(blocked) {
873
+ const lines = [];
874
+ for (const evalId of blocked.conflictingEvals ?? []) {
875
+ lines.push(` ${evalId}`);
876
+ }
877
+ if (blocked.detail !== undefined)
878
+ lines.push(` ${blocked.detail}`);
879
+ return lines;
880
+ }
881
+ function renderExperimentRenameRejectedDetail(rejected) {
882
+ const lines = [];
883
+ const conflicting = rejected.conflictingEvals ?? [];
884
+ if (conflicting.length > 0) {
885
+ lines.push((0, index_ts_5.t)("cli.rename.conflicting", { evals: conflicting.join(", ") }));
886
+ }
887
+ return lines;
888
+ }
889
+ /** 机器面:单份 JSON 文档,不混入人读文本(见 rename.md「命令」)。 */
890
+ function renderExperimentRenameJson(document) {
891
+ return `${JSON.stringify({
892
+ format: exports.EXPERIMENT_RENAME_FORMAT,
893
+ schemaVersion: exports.EXPERIMENT_RENAME_SCHEMA_VERSION,
894
+ ...document,
895
+ })}\n`;
896
+ }
897
+ /** 把 renameExperiment 抛出的资格错误投影成 rejected 文档(JSON 与拒绝人读共用)。 */
898
+ function experimentRenameRejectedFromError(error) {
899
+ const plan = error.plan;
900
+ const blocked = plan?.blocked;
901
+ return {
902
+ status: "rejected",
903
+ oldId: plan?.oldId ?? "",
904
+ newId: plan?.newId ?? "",
905
+ reason: error.reason,
906
+ ...(blocked?.evalId === undefined ? {} : { evalId: blocked.evalId }),
907
+ ...(blocked?.conflictingEvals === undefined ? {} : { conflictingEvals: blocked.conflictingEvals }),
908
+ ...(blocked?.detail === undefined ? {} : { detail: blocked.detail }),
909
+ };
910
+ }
911
+ /** 退出码:可迁移预览与成功执行为 0;blocked 预览 / rejected 整批零写入按失败退出。 */
912
+ function experimentRenameExitCode(document) {
913
+ if (document.status === "done")
914
+ return 0;
915
+ if (document.status === "plan")
916
+ return document.blocked === undefined ? 0 : 1;
917
+ return 1;
918
+ }
919
+ /**
920
+ * exp rename 的 CLI 执行:解析 → 核心 → 渲染 → 退出码。核心契约见
921
+ * src/runner/rename-experiment.ts(planExperimentRename / renameExperiment)。
922
+ * renameExperiment 对资格失败抛 ExperimentRenameError,CLI 把它投影成 rejected 文档。
923
+ */
924
+ function runExperimentRenameCommand(cwd, args, flags) {
925
+ return effect_1.Effect.gen(function* () {
926
+ const parsed = parseExperimentRenamePositionals(args);
927
+ if (!parsed.ok) {
928
+ yield* writeStderr((0, index_ts_5.t)("cli.rename.usage"));
929
+ return 1;
930
+ }
931
+ const unsupported = firstExperimentRenameUnsupportedFlag(flags);
932
+ if (unsupported !== undefined) {
933
+ yield* writeStderr((0, index_ts_5.t)("cli.rename.flagUnsupported", { flag: unsupported }));
934
+ return 1;
935
+ }
936
+ const { oldId, newId } = parsed;
937
+ const mod = (yield* cliPromise("load experiment rename command", () => __niceevalDynamicImport("../runner/rename-experiment.cjs")));
938
+ if (flags.dry) {
939
+ const plan = yield* cliEffect("plan experiment rename", mod.planExperimentRename({ cwd, oldId, newId }));
940
+ yield* writeStdout(flags.json ? renderExperimentRenameJson(plan) : renderExperimentRenamePlanHuman(plan));
941
+ return experimentRenameExitCode(plan);
942
+ }
943
+ const outcome = yield* effect_1.Effect.either(cliEffect("rename experiment", mod.renameExperiment({ cwd, oldId, newId })));
944
+ if (effect_1.Either.isRight(outcome)) {
945
+ yield* writeStdout(flags.json ? renderExperimentRenameJson(outcome.right) : renderExperimentRenameDoneHuman(outcome.right));
946
+ return 0;
947
+ }
948
+ // ExperimentRenameError 携带稳定 reason 与 rejected plan;其它异常按通用失败兜底。
949
+ const error = outcome.left.cause;
950
+ if (error instanceof rename_experiment_ts_1.ExperimentRenameError) {
951
+ const rejected = experimentRenameRejectedFromError(error);
952
+ yield* writeStdout(flags.json ? renderExperimentRenameJson(rejected) : renderExperimentRenameRejectedHuman(rejected));
953
+ }
954
+ else {
955
+ yield* writeStderr((0, index_ts_5.t)("cli.rename.failed", { error: error instanceof Error ? error.message : String(error) }));
956
+ }
957
+ return 1;
958
+ });
959
+ }
960
+ function loadConfig(cwd) {
961
+ return effect_1.Effect.gen(function* () {
962
+ const project = yield* project_configuration_ts_1.ProjectConfiguration;
963
+ return yield* project.load(cwd).pipe(effect_1.Effect.mapError((cause) => cliFailure("load NiceEval config", cause)));
964
+ });
965
+ }
966
+ // AGENTS.md/CLAUDE.md 托管区块:告诉在这个项目里干活的 coding agent「niceeval 不在你的训练数据里,
967
+ // 先读随包文档,跑完只经公开 show 面诊断」。随包只发中文准绳版文档(英文站是手工同步、可能滞后,
968
+ // 不进包,见 package.json 的 files);init 时写入/刷新;标记之外的用户内容永不触碰。
969
+ function initProject(cwd) {
970
+ return effect_1.Effect.flatMap(application_ts_1.ProjectInitializer, (initializer) => initializer.initialize(cwd)).pipe(effect_1.Effect.mapError((cause) => cliFailure("initialize project", cause)));
971
+ }
972
+ function openBrowser(url) {
973
+ return effect_1.Effect.flatMap(application_ts_1.BrowserLauncher, (launcher) => launcher.open(url)).pipe(effect_1.Effect.mapError((cause) => cliFailure("open browser", cause)));
974
+ }
975
+ /**
976
+ * run 结束后把 coordinator 累计的诊断折成 `InvocationCompletion`(见 docs/feature/experiments/cli.md
977
+ * 「运行完成状态不只看 verdict 计数」)。只读已经真实发生过的诊断,不额外发明信号:
978
+ * - `"interrupted"` 诊断只在 run.ts 判定为真·中断(Effect exit 真实标记中断,不是「signal 被
979
+ * abort 过」这种更弱的信号)时才会出现,见 `runner/run.ts` 的 `reportInterrupted()` 调用点。
980
+ * - `"budget-exhausted:<experimentId>"` 诊断的 `count` 就是该 experiment 因预算耗尽未派发的
981
+ * attempt 数(见 `runner/feedback/reducer.ts` 对 `budget-exhausted` 事件的注释),跨
982
+ * experiment 求和得到 `unstarted`。
983
+ * - `"reporter-error:<reporter>"` 诊断转成 `ReporterError[]`;`required` 字段来自事件自带的
984
+ * `data.required`,直接反映这个 reporter 注册时的真实 required/best-effort 分类(见上面
985
+ * 构造 `reporters: ReporterRegistration[]` 的地方——`--junit` 为 `required: true`,
986
+ * `config.reporters` 恒 `false`),不是一个统一写死的占位值。
987
+ * - `earlyExitUnstarted` 从反馈状态的 attempt:early-exit 计数派生(减去 fail-fast 的那部分——
988
+ * 那是「未完整覆盖」,进 unstarted,不是「省下的重复验证」)。
989
+ */
990
+ function assembleInvocationCompletion(state) {
991
+ let unstarted = 0;
992
+ let failFastSkipped = 0;
993
+ let haltedSkipped = 0;
994
+ let interrupted = false;
995
+ const reporterErrors = [];
996
+ for (const d of state.diagnostics) {
997
+ // 归类按**稳定词法** `code`,不按 `key`:`key` 里编着折叠身份(experimentId / evalId /
998
+ // reporter 名),拿它做前缀匹配会在「把身份从 key 里摘出去」时静默失配——记账悄悄归零,
999
+ // 没有任何测试或类型会报警。`code` 省略时回落到 key 的首段:缺省 key 恒是
1000
+ // `${code}:${identity}`(见 sink.ts 的 DiagnosticInput),首段即 code。
1001
+ const code = d.code ?? d.key.split(":", 1)[0];
1002
+ if (code === "interrupted") {
1003
+ interrupted = true;
1004
+ }
1005
+ else if (code === "budget-exhausted") {
1006
+ unstarted += d.count;
1007
+ }
1008
+ else if (code === "fail-fast") {
1009
+ // run 级 fail-fast 造成的未派发同样计入 unstarted(结论落 incomplete,见
1010
+ // docs/feature/experiments/architecture.md「Completion 与退出」)。
1011
+ unstarted += d.count;
1012
+ failFastSkipped += d.count;
1013
+ }
1014
+ else if (code === "dispatch-halted") {
1015
+ // 止损闸停派发造成的未派发(见 docs/feature/error-classification/architecture.md
1016
+ // 「记账」)。这条诊断的 count 是「同一死因被声明了几次」(重复声明折叠),不是未派发数——
1017
+ // 未派发数由 emitter 累计后写在 data.unstarted 里(与 budget-exhausted 同一口径)。
1018
+ const halted = typeof d.data?.unstarted === "number" ? d.data.unstarted : 0;
1019
+ unstarted += halted;
1020
+ haltedSkipped += halted;
1021
+ }
1022
+ else if (code === "reporter-error") {
1023
+ // required 决定这条错误是否写进 InvocationCompletion.reporterErrors 并让 completion 非 complete
1024
+ // (见 docs/cli.md「required reporter」);best-effort reporter 的失败只保留为 diagnostic。
1025
+ if (d.data?.required !== true)
1026
+ continue;
1027
+ reporterErrors.push({
1028
+ reporter: typeof d.data?.reporter === "string" ? d.data.reporter : d.key.slice("reporter-error:".length),
1029
+ required: true,
1030
+ message: d.message,
1031
+ });
1032
+ }
1033
+ }
1034
+ // 中断造成的未派发(仍在 queued 的 attempt)同样计入 unstarted(见 docs/feature/experiments/
1035
+ // architecture.md「Completion 与退出」:budget 耗尽、fail-fast 或中断造成的未派发都不伪装成全绿)。
1036
+ if (interrupted)
1037
+ unstarted += state.queued;
1038
+ // attempt:early-exit 计数含 fail-fast 与止损闸的未派发(反馈层同一事件驱动计数守恒);
1039
+ // 「省下的重复验证」= 总数减去那两部分。
1040
+ const earlyExitUnstarted = Math.max(0, state.earlyExitSkipped - failFastSkipped - haltedSkipped);
1041
+ const status = interrupted
1042
+ ? "interrupted"
1043
+ : unstarted > 0 || reporterErrors.length > 0
1044
+ ? "incomplete"
1045
+ : "complete";
1046
+ return { status, unstarted, earlyExitUnstarted, reporterErrors };
1047
+ }
1048
+ /** package.json 的 version 字段;-v/--version 直接回显这个号。 */
1049
+ function packageVersion() {
1050
+ return effect_1.Effect.flatMap(application_ts_1.PackageMetadata, (metadata) => metadata.version).pipe(effect_1.Effect.mapError((cause) => cliFailure("read package version", cause)));
1051
+ }
1052
+ function writeStdout(text) {
1053
+ return effect_1.Effect.flatMap(application_ts_1.CliOutput, (output) => output.writeStdout(text)).pipe(effect_1.Effect.mapError((cause) => cliFailure("write stdout", cause)));
1054
+ }
1055
+ function writeStderr(text) {
1056
+ return effect_1.Effect.flatMap(application_ts_1.CliOutput, (output) => output.writeStderr(text)).pipe(effect_1.Effect.mapError((cause) => cliFailure("write stderr", cause)));
1057
+ }
1058
+ /** Idempotent application-level sweep for resources owned by this invocation. */
1059
+ function releaseCliResources() {
1060
+ // Shared-state leases are never part of this exit sweep. A normal Runner
1061
+ // lifecycle releases one only after every pool/provider/Experiment cleanup
1062
+ // succeeds; a failed cleanup intentionally leaves explicit recovery work.
1063
+ return cliPromise("stop remaining sandboxes", () => (0, registry_ts_1.stopAllSandboxes)()).pipe(effect_1.Effect.ignore, effect_1.Effect.zipRight((0, experiment_cleanup_registry_ts_1.drainExperimentTeardowns)().pipe(effect_1.Effect.ignore)), effect_1.Effect.zipRight((0, lock_ts_1.drainHeldCaseLocksEffect)().pipe(effect_1.Effect.ignore)));
1064
+ }
1065
+ function errorMessage(cause) {
1066
+ return cause instanceof Error ? cause.message : String(cause);
1067
+ }
1068
+ function runDockerCommand(positionals, flags) {
1069
+ return effect_1.Effect.gen(function* () {
1070
+ const { runDockerProfileCommand } = (yield* cliPromise("load Docker profile command", () => __niceevalDynamicImport("../sandbox/docker-profile/cli.cjs")));
1071
+ return yield* cliPromise("run Docker profile command", () => runDockerProfileCommand(positionals, { json: flags.json, smoke: flags.smoke }));
1072
+ });
1073
+ }
1074
+ function runSandboxCliCommand(cwd, positionals, flags) {
1075
+ return cliEffect("run sandbox command", (0, cli_commands_ts_1.runSandboxCommandEffect)(cwd, [...positionals], {
1076
+ all: flags.all,
1077
+ window: flags.window,
1078
+ path: flags.sandboxPath,
1079
+ leaveRunning: flags.leaveRunning,
1080
+ run: flags.record,
1081
+ orphans: flags.orphans,
1082
+ force: flags.force,
1083
+ }));
1084
+ }
1085
+ function runSessionCommand(cwd, positionals, flags) {
1086
+ return effect_1.Effect.gen(function* () {
1087
+ const path = yield* application_ts_1.CliPath;
1088
+ const niceevalRoot = path.resolve(cwd, ".niceeval");
1089
+ const subcommand = positionals[0] ?? "list";
1090
+ if (subcommand === "list") {
1091
+ if (positionals.length > 2) {
1092
+ yield* writeStderr("niceeval session list accepts at most one experiment prefix.\n");
1093
+ return 1;
1094
+ }
1095
+ const outcome = yield* effect_1.Effect.either(cliEffect("list sessions", (0, session_ts_1.listSessions)(niceevalRoot, {
1096
+ all: flags.all,
1097
+ ...(positionals[1] === undefined ? {} : { selector: positionals[1] }),
1098
+ })));
1099
+ if (effect_1.Either.isLeft(outcome)) {
1100
+ yield* writeStderr(`${errorMessage(outcome.left.cause)}\n`);
1101
+ return 1;
1102
+ }
1103
+ yield* writeStdout(flags.json
1104
+ ? `${JSON.stringify(outcome.right)}\n`
1105
+ : (0, session_ts_1.renderSessionListText)(outcome.right, Date.now(), flags.all));
1106
+ return 0;
1107
+ }
1108
+ if (subcommand === "show") {
1109
+ if (positionals.length !== 2 || flags.all) {
1110
+ yield* writeStderr("Usage: niceeval session show <sessionId> [--json]\n");
1111
+ return 1;
1112
+ }
1113
+ const outcome = yield* effect_1.Effect.either(cliEffect("show session", (0, session_ts_1.showSession)(niceevalRoot, positionals[1])));
1114
+ if (effect_1.Either.isLeft(outcome)) {
1115
+ yield* writeStderr(`${errorMessage(outcome.left.cause)}\n`);
1116
+ return 1;
1117
+ }
1118
+ yield* writeStdout(flags.json
1119
+ ? `${JSON.stringify(outcome.right)}\n`
1120
+ : (0, session_ts_1.renderSessionShowText)(outcome.right));
1121
+ return 0;
1122
+ }
1123
+ yield* writeStderr("Usage: niceeval session list [--all] [<experiment-prefix>] [--json]\n" +
1124
+ " niceeval session show <sessionId> [--json]\n");
1125
+ return 1;
1126
+ });
1127
+ }
1128
+ function runRecordMaintenanceCommand(cwd, command, positionals, flags) {
1129
+ return effect_1.Effect.gen(function* () {
1130
+ if (positionals.length > 0) {
1131
+ yield* writeStderr(`niceeval ${command} does not accept positional arguments.\n`);
1132
+ return 1;
1133
+ }
1134
+ const unsupported = firstRecordMaintenanceUnsupportedFlag(flags);
1135
+ if (unsupported !== undefined) {
1136
+ yield* writeStderr(`niceeval ${command} does not accept ${unsupported}.\n`);
1137
+ return 1;
1138
+ }
1139
+ const result = yield* cliEffect("run Record maintenance", (0, record_ts_1.runRecordCliCommand)({
1140
+ command,
1141
+ cwd,
1142
+ ...(flags.record === undefined ? {} : { record: flags.record }),
1143
+ yes: flags.yes,
1144
+ }));
1145
+ if (result.stdout !== "")
1146
+ yield* writeStdout(result.stdout);
1147
+ if (result.stderr !== "")
1148
+ yield* writeStderr(result.stderr);
1149
+ return result.exitCode;
1150
+ });
1151
+ }
1152
+ function agentRunFromExperiment(experiment, selectedEvalIds, overrides = {}) {
1153
+ return {
1154
+ agent: experiment.agent,
1155
+ model: experiment.model,
1156
+ reasoningEffort: experiment.reasoningEffort,
1157
+ flags: experiment.flags ?? {},
1158
+ plugins: experiment.plugins,
1159
+ attempts: overrides.attempts ?? experiment.attempts ?? 1,
1160
+ earlyExit: overrides.earlyExit ?? experiment.earlyExit ?? false,
1161
+ sandbox: experiment.sandbox,
1162
+ sandboxReuse: experiment.sandboxReuse,
1163
+ ...(experiment.sharedState === undefined ? {} : { sharedState: experiment.sharedState }),
1164
+ judge: experiment.judge,
1165
+ ...(0, timeout_ts_1.resolveRunTimeout)(overrides.timeout, experiment.timeoutMs),
1166
+ budget: overrides.budget ?? experiment.budget,
1167
+ selectedEvalIds,
1168
+ experimentId: experiment.id,
1169
+ experimentBaseDir: experiment.baseDir,
1170
+ experimentSourcePath: experiment.sourcePath,
1171
+ description: experiment.description,
1172
+ labels: experiment.labels,
1173
+ maxConcurrency: experiment.maxConcurrency,
1174
+ setup: experiment.setup,
1175
+ teardown: experiment.teardown,
1176
+ classifyFailure: experiment.classifyFailure,
1177
+ };
1178
+ }
1179
+ function hasSharedStateRecoveryOptions(flags) {
1180
+ return flags.recoverSharedState !== undefined ||
1181
+ flags.ownerToken !== undefined ||
1182
+ flags.confirmOwnerTerminated ||
1183
+ flags.confirmRemoteQuiesced;
1184
+ }
1185
+ function renderSharedStateRecoveryTarget(record) {
1186
+ return (0, index_ts_5.t)("cli.exp.sharedStateRecoveryTarget", {
1187
+ key: record.key,
1188
+ experimentId: record.experimentId,
1189
+ ownerToken: record.ownerToken,
1190
+ host: record.host,
1191
+ pid: record.pid,
1192
+ processIdentity: record.processIdentity,
1193
+ heartbeatAt: record.heartbeatAt,
1194
+ });
1195
+ }
1196
+ function experimentTeardownOf(experiment) {
1197
+ return typeof experiment.teardown === "function" ? experiment.teardown : undefined;
1198
+ }
1199
+ /**
1200
+ * A legacy free generation can exist from an interrupted pre-fix recovery:
1201
+ * it has durable owner evidence but may still have its old teardown entry.
1202
+ * Only the entry keyed by that exact immutable owner is eligible for removal.
1203
+ *
1204
+ * Claimed recovery takes the same path before publishing free. A failed or
1205
+ * contested claim therefore leaves the recovery generation closed rather than
1206
+ * letting a waiter enter while the old teardown could still be replayed.
1207
+ */
1208
+ function inspectExactRecoveryTeardownRegistrationEffect(input) {
1209
+ const { niceevalRoot, record, currentHost } = input;
1210
+ const id = (0, teardown_registry_ts_1.teardownEntryId)(record.experimentId, record.pid);
1211
+ return effect_1.Effect.gen(function* () {
1212
+ const registration = yield* (0, teardown_registry_ts_1.readExactTeardownRegistrationEffect)(niceevalRoot, record.experimentId, record.pid);
1213
+ // A holder can die before its registration is durably written. There is
1214
+ // then no old teardown obligation to clear, but no unrelated entry may be
1215
+ // inferred from the current Experiment declaration.
1216
+ if (registration === undefined)
1217
+ return false;
1218
+ if (registration.host !== record.host) {
1219
+ return yield* effect_1.Effect.fail(new Error(`teardown registration ${JSON.stringify(id)} belongs to host ${JSON.stringify(registration.host)}, ` +
1220
+ `not immutable sharedState owner host ${JSON.stringify(record.host)}`));
1221
+ }
1222
+ // The registration does not carry a process identity itself, so recovery
1223
+ // compares it to the immutable sharedState owner. This shares the lease
1224
+ // terminal boundary: an exact local Z/X/x owner is stopped; an unreadable
1225
+ // or malformed identity probe fails closed instead of treating PID
1226
+ // existence as proof that cleanup can still run.
1227
+ const terminated = yield* (0, teardown_registry_ts_1.isExactTeardownRegistrationOwnerTerminatedEffect)({
1228
+ registration,
1229
+ owner: record,
1230
+ currentHost,
1231
+ });
1232
+ if (!terminated) {
1233
+ return yield* effect_1.Effect.fail(new Error(`teardown registration ${JSON.stringify(id)} still belongs to a live exact sharedState owner; refusing to clear it`));
1234
+ }
1235
+ return true;
1236
+ });
1237
+ }
1238
+ function clearExactRecoveryTeardownRegistrationEffect(input) {
1239
+ const { niceevalRoot, record, currentHost, expectedRegistration } = input;
1240
+ const id = (0, teardown_registry_ts_1.teardownEntryId)(record.experimentId, record.pid);
1241
+ return effect_1.Effect.gen(function* () {
1242
+ const present = yield* inspectExactRecoveryTeardownRegistrationEffect({
1243
+ niceevalRoot,
1244
+ record,
1245
+ currentHost,
1246
+ });
1247
+ if (!present) {
1248
+ if (expectedRegistration) {
1249
+ return yield* effect_1.Effect.fail(new Error(`teardown registration ${JSON.stringify(id)} disappeared before this recovery could clear it`));
1250
+ }
1251
+ return;
1252
+ }
1253
+ if (!expectedRegistration) {
1254
+ return yield* effect_1.Effect.fail(new Error(`teardown registration ${JSON.stringify(id)} appeared after this recovery was claimed`));
1255
+ }
1256
+ const removed = yield* (0, teardown_registry_ts_1.removeTeardownRegistrationIfPresentEffect)(niceevalRoot, id);
1257
+ if (!removed) {
1258
+ return yield* effect_1.Effect.fail(new Error(`teardown registration ${JSON.stringify(id)} changed before this recovery could clear it`));
1259
+ }
1260
+ });
1261
+ }
1262
+ function renderDebugPlanJson(input) {
1263
+ return `${JSON.stringify({
1264
+ format: "niceeval.debug-plan/v1",
1265
+ schemaVersion: 1,
1266
+ experimentId: input.experimentId,
1267
+ evalId: input.evalId,
1268
+ commandPlan: input.commandPlan,
1269
+ })}\n`;
1270
+ }
1271
+ function runDebugCommand(cwd, positionals, flags) {
1272
+ return effect_1.Effect.gen(function* () {
1273
+ const facts = yield* invocationFacts;
1274
+ if (positionals.length !== 2) {
1275
+ yield* writeStderr((0, index_ts_5.t)("cli.debug.usage"));
1276
+ return 1;
1277
+ }
1278
+ const [experimentSelector, evalSelector] = positionals;
1279
+ const config = yield* loadConfig(cwd);
1280
+ const result = yield* cliEffect("debug Experiment lifecycle", index_ts_1.experimentHost.debug({
1281
+ cwd,
1282
+ config,
1283
+ experimentSelector,
1284
+ evalSelector,
1285
+ }));
1286
+ switch (result.status) {
1287
+ case "experiment-no-match":
1288
+ yield* writeStderr((0, index_ts_5.t)("cli.debug.experimentNoMatch", {
1289
+ selector: result.selector,
1290
+ candidates: result.candidates.join(", ") || (0, index_ts_5.t)("cli.none"),
1291
+ }));
1292
+ return 1;
1293
+ case "experiment-ambiguous":
1294
+ yield* writeStderr((0, index_ts_5.t)("cli.debug.experimentAmbiguous", {
1295
+ selector: result.selector,
1296
+ candidates: result.candidates.join(", "),
1297
+ }));
1298
+ return 1;
1299
+ case "eval-no-match":
1300
+ yield* writeStderr((0, index_ts_5.t)("cli.debug.evalNoMatch", {
1301
+ selector: result.selector,
1302
+ experimentId: result.experimentId,
1303
+ candidates: result.candidates.join(", ") || (0, index_ts_5.t)("cli.none"),
1304
+ }));
1305
+ return 1;
1306
+ case "eval-ambiguous":
1307
+ yield* writeStderr((0, index_ts_5.t)("cli.debug.evalAmbiguous", {
1308
+ selector: result.selector,
1309
+ experimentId: result.experimentId,
1310
+ candidates: result.candidates.join(", "),
1311
+ }));
1312
+ return 1;
1313
+ case "planned":
1314
+ yield* writeStdout(flags.json
1315
+ ? renderDebugPlanJson(result)
1316
+ : (0, index_ts_4.renderHumanCommandPlan)(result.commandPlan, {
1317
+ isTTY: facts.stdout.isTTY,
1318
+ noColor: facts.noColor,
1319
+ width: facts.stdout.columns,
1320
+ }));
1321
+ return 0;
1322
+ }
1323
+ });
1324
+ }
1325
+ function runEvaluationCommand(cwd, command, positionals, flags, interruption) {
1326
+ return effect_1.Effect.gen(function* () {
1327
+ const facts = yield* invocationFacts;
1328
+ const cliOutput = yield* application_ts_1.CliOutput;
1329
+ const path = yield* application_ts_1.CliPath;
1330
+ const terminal = yield* application_ts_1.CliTerminal;
1331
+ // Explicit recovery is intentionally human-only. It has no receipt or
1332
+ // NDJSON contract, so accepting --json would give machine callers an
1333
+ // apparently successful but unparseable interface.
1334
+ if (command === "exp" && flags.recoverSharedState !== undefined && flags.json) {
1335
+ yield* writeStderr((0, index_ts_5.t)("cli.exp.sharedStateRecoveryJsonUnsupported"));
1336
+ return 1;
1337
+ }
1338
+ // Keep project-local coordination (sessions, locks, kept sandboxes) separate
1339
+ // from the portable Record root selected by `--record` below.
1340
+ const coordinationRoot = path.resolve(cwd, ".niceeval");
1341
+ const config = yield* loadConfig(cwd);
1342
+ const maxBuildConcurrency = flags.maxBuildConcurrency ?? config.maxBuildConcurrency ?? 2;
1343
+ if (!Number.isInteger(maxBuildConcurrency) || maxBuildConcurrency <= 0) {
1344
+ yield* writeStderr(`maxBuildConcurrency must be a positive integer, got ${maxBuildConcurrency}.\n`);
1345
+ return 1;
1346
+ }
1347
+ let evals = Object.freeze([]);
1348
+ if (command === "list") {
1349
+ const listed = yield* cliEffect("list evals", index_ts_1.experimentHost.list({
1350
+ cwd,
1351
+ ...(flags.tag === undefined ? {} : { tag: flags.tag }),
1352
+ }));
1353
+ evals = listed.evals;
1354
+ yield* writeStdout((0, index_ts_5.t)("cli.list.header", { count: evals.length }));
1355
+ for (const evalDefinition of evals) {
1356
+ yield* writeStdout(` ${evalDefinition.id}${evalDefinition.description ? ` — ${evalDefinition.description}` : ""}\n`);
1357
+ }
1358
+ return 0;
1359
+ }
1360
+ const agentRuns = [];
1361
+ let experimentSelection = (0, index_ts_5.t)("cli.all");
1362
+ let availableExperimentPaths = (0, index_ts_5.t)("cli.none");
1363
+ if (command === "exp" || command === "check") {
1364
+ if (hasSharedStateRecoveryOptions(flags) && (command !== "exp" || !flags.teardown)) {
1365
+ yield* writeStderr((0, index_ts_5.t)("cli.exp.sharedStateRecoveryFlags"));
1366
+ return 1;
1367
+ }
1368
+ if (flags.agent || flags.model) {
1369
+ yield* writeStderr((0, index_ts_5.t)("cli.exp.agentModelFlagUnsupported"));
1370
+ return 1;
1371
+ }
1372
+ if (flags.force) {
1373
+ yield* writeStderr((0, index_ts_5.t)("cli.exp.forceUnsupported"));
1374
+ return 1;
1375
+ }
1376
+ // `--record` selects the fact root for `exp`; `check` has no Record
1377
+ // reader or writer, so accepting it there would silently discard input.
1378
+ if (command === "check" && flags.record !== undefined) {
1379
+ yield* writeStderr((0, index_ts_5.t)("cli.check.recordUnsupported"));
1380
+ return 1;
1381
+ }
1382
+ const viewerFlag = firstViewerOnlyFlag(flags);
1383
+ if (viewerFlag) {
1384
+ yield* writeStderr((0, index_ts_5.t)("cli.exp.viewerFlagUnsupported", { flag: viewerFlag.flag, command: viewerFlag.command }));
1385
+ return 1;
1386
+ }
1387
+ if (command === "exp" && positionals[0] === "list") {
1388
+ if (positionals.length > 2) {
1389
+ yield* writeStderr("niceeval exp list accepts at most one experiment prefix.\n");
1390
+ return 1;
1391
+ }
1392
+ const selector = positionals[1];
1393
+ const listed = yield* cliEffect("list experiments", index_ts_1.experimentHost.list({
1394
+ cwd,
1395
+ ...(flags.tag === undefined ? {} : { tag: flags.tag }),
1396
+ ...(selector === undefined ? {} : { selector }),
1397
+ }));
1398
+ if (listed.selections.length === 0 && selector !== undefined) {
1399
+ yield* writeStderr((0, index_ts_5.t)("cli.experiment.noMatch", {
1400
+ arg: selector,
1401
+ experiments: (0, aggregate_ts_1.browsableExperimentPaths)(listed.experimentIds).join(", ") || (0, index_ts_5.t)("cli.none"),
1402
+ }));
1403
+ return 1;
1404
+ }
1405
+ const rows = listed.selections.map(({ experiment, selectedEvalIds }) => {
1406
+ return {
1407
+ experimentId: experiment.id,
1408
+ ...(experiment.description !== undefined ? { description: experiment.description } : {}),
1409
+ agent: experiment.agent.name,
1410
+ ...(experiment.model !== undefined ? { model: experiment.model } : {}),
1411
+ attempts: experiment.attempts,
1412
+ evalCount: selectedEvalIds.length,
1413
+ labels: { ...experiment.labels },
1414
+ selectedEvalIds,
1415
+ };
1416
+ });
1417
+ if (flags.json) {
1418
+ yield* writeStdout(`${JSON.stringify({ format: "niceeval.experiments", schemaVersion: 1, experiments: rows })}\n`);
1419
+ }
1420
+ else {
1421
+ for (const row of rows) {
1422
+ yield* writeStdout([
1423
+ row.experimentId,
1424
+ row.description ?? "—",
1425
+ row.agent,
1426
+ row.model ?? "—",
1427
+ `attempts=${row.attempts}`,
1428
+ `evals=${row.evalCount}`,
1429
+ `labels=${JSON.stringify(row.labels)}`,
1430
+ ].join("\t") + "\n");
1431
+ }
1432
+ }
1433
+ return 0;
1434
+ }
1435
+ const expArg = positionals[0];
1436
+ const extraPatterns = positionals.slice(1);
1437
+ experimentSelection = positionals.join(" ") || (0, index_ts_5.t)("cli.all");
1438
+ const listed = yield* cliEffect("select experiments", index_ts_1.experimentHost.list({
1439
+ cwd,
1440
+ ...(flags.tag === undefined ? {} : { tag: flags.tag }),
1441
+ ...(expArg === undefined ? {} : { selector: expArg }),
1442
+ ...(extraPatterns.length === 0 ? {} : { evalPatterns: extraPatterns }),
1443
+ }));
1444
+ evals = listed.evals;
1445
+ const selected = listed.selections;
1446
+ availableExperimentPaths = (0, aggregate_ts_1.browsableExperimentPaths)(listed.experimentIds).join(", ") || (0, index_ts_5.t)("cli.none");
1447
+ if (selected.length === 0) {
1448
+ yield* writeStderr((0, index_ts_5.t)("cli.experiment.noMatch", {
1449
+ arg: expArg ?? (0, index_ts_5.t)("cli.all"),
1450
+ experiments: availableExperimentPaths,
1451
+ }));
1452
+ if (expArg === "show" || expArg === "view") {
1453
+ yield* writeStderr((0, index_ts_5.t)("cli.experiment.viewerCommandHint", {
1454
+ command: expArg,
1455
+ args: extraPatterns.length > 0 ? ` ${extraPatterns.join(" ")}` : "",
1456
+ }));
1457
+ }
1458
+ return 1;
1459
+ }
1460
+ const reminder = yield* (0, cli_commands_ts_1.keptSandboxReminderEffect)(cwd).pipe(effect_1.Effect.catchAll(() => effect_1.Effect.succeed(undefined)));
1461
+ if (reminder)
1462
+ yield* writeStderr(reminder);
1463
+ const orphans = yield* (0, cli_commands_ts_1.orphanReminderEffect)(cwd).pipe(effect_1.Effect.catchAll(() => effect_1.Effect.succeed(undefined)));
1464
+ if (orphans)
1465
+ yield* writeStderr(orphans);
1466
+ const teardownReminder = yield* (0, teardown_registry_ts_1.orphanedTeardownReminderEffect)(coordinationRoot, new Set(selected
1467
+ .filter(({ experiment }) => experimentTeardownOf(experiment) !== undefined)
1468
+ .map(({ experiment }) => experiment.id)), facts.hostname).pipe(effect_1.Effect.catchAll(() => effect_1.Effect.succeed(undefined)));
1469
+ if (teardownReminder)
1470
+ yield* writeStderr(teardownReminder);
1471
+ if (flags.teardown) {
1472
+ if (extraPatterns.length > 0) {
1473
+ yield* writeStderr((0, index_ts_5.t)("cli.exp.teardownNoEvalPatterns"));
1474
+ return 1;
1475
+ }
1476
+ const niceevalRootForTeardown = coordinationRoot;
1477
+ let selectedForTeardown = selected;
1478
+ let explicitRecovery;
1479
+ if (flags.recoverSharedState !== undefined) {
1480
+ // The input key is the recovery authority. Read its immutable
1481
+ // evidence before considering today's Experiment declaration: an
1482
+ // author may have renamed or removed sharedState after the holder
1483
+ // crashed, but must still have a public recovery path for that old
1484
+ // external state.
1485
+ const inspected = yield* effect_1.Effect.either((0, shared_state_lease_ts_1.readSharedStateLeaseRecoveryTargetEffect)(niceevalRootForTeardown, flags.recoverSharedState));
1486
+ if (effect_1.Either.isLeft(inspected)) {
1487
+ yield* writeStderr(`sharedState recovery inspection failed: ${errorMessage(inspected.left)}\n`);
1488
+ return 1;
1489
+ }
1490
+ if (inspected.right === undefined) {
1491
+ yield* writeStderr(`No recoverable sharedState ownership evidence exists for ${JSON.stringify(flags.recoverSharedState)}.\n`);
1492
+ return 1;
1493
+ }
1494
+ yield* writeStderr(renderSharedStateRecoveryTarget(inspected.right));
1495
+ if (selected.length !== 1) {
1496
+ yield* writeStderr("sharedState recovery requires an experiment selector that resolves to exactly one Experiment.\n");
1497
+ return 1;
1498
+ }
1499
+ const target = selected[0];
1500
+ if (inspected.right.experimentId !== target.experiment.id) {
1501
+ yield* writeStderr(`sharedState key ${JSON.stringify(flags.recoverSharedState)} belongs to experiment ${inspected.right.experimentId}, not ${target.experiment.id}; refusing recovery.\n`);
1502
+ return 1;
1503
+ }
1504
+ // Inspection intentionally precedes confirmation validation. The
1505
+ // immutable generation head retains the exact owner evidence even
1506
+ // after a crashed recovery attempt; this public command is the only
1507
+ // supported way to reveal it, never private-file manipulation.
1508
+ if (flags.ownerToken === undefined ||
1509
+ !flags.confirmOwnerTerminated ||
1510
+ !flags.confirmRemoteQuiesced) {
1511
+ yield* writeStderr((0, index_ts_5.t)("cli.exp.sharedStateRecoveryFlags"));
1512
+ return 1;
1513
+ }
1514
+ // Inspection is read-only and remains available without a teardown.
1515
+ // Only the uniquely selected target can reach this point. Validate
1516
+ // its teardown immediately before claiming a recovery generation.
1517
+ if (experimentTeardownOf(target.experiment) === undefined) {
1518
+ yield* writeStderr((0, index_ts_5.t)("cli.exp.sharedStateRecoveryTeardownRequired", {
1519
+ experimentId: target.experiment.id,
1520
+ }));
1521
+ return 1;
1522
+ }
1523
+ const begun = yield* effect_1.Effect.either((0, shared_state_lease_ts_1.beginExplicitSharedStateRecoveryEffect)({
1524
+ niceevalRoot: niceevalRootForTeardown,
1525
+ key: flags.recoverSharedState,
1526
+ ownerToken: flags.ownerToken,
1527
+ localHost: facts.hostname,
1528
+ confirmOwnerTerminated: flags.confirmOwnerTerminated,
1529
+ confirmRemoteQuiesced: flags.confirmRemoteQuiesced,
1530
+ }));
1531
+ if (effect_1.Either.isLeft(begun)) {
1532
+ yield* writeStderr(`sharedState recovery refused: ${errorMessage(begun.left)}\n`);
1533
+ return 1;
1534
+ }
1535
+ if (begun.right._tag === "AlreadyReleased") {
1536
+ const registrationWasPresent = yield* effect_1.Effect.either(inspectExactRecoveryTeardownRegistrationEffect({
1537
+ niceevalRoot: niceevalRootForTeardown,
1538
+ record: begun.right.record,
1539
+ currentHost: facts.hostname,
1540
+ }));
1541
+ if (effect_1.Either.isLeft(registrationWasPresent)) {
1542
+ yield* writeStderr((0, index_ts_5.t)("cli.exp.sharedStateRecoveryAlreadyReleasedRegistrationFailed", {
1543
+ key: begun.right.record.key,
1544
+ message: errorMessage(registrationWasPresent.left),
1545
+ }));
1546
+ return 1;
1547
+ }
1548
+ const cleared = yield* effect_1.Effect.either(clearExactRecoveryTeardownRegistrationEffect({
1549
+ niceevalRoot: niceevalRootForTeardown,
1550
+ record: begun.right.record,
1551
+ currentHost: facts.hostname,
1552
+ expectedRegistration: registrationWasPresent.right,
1553
+ }));
1554
+ if (effect_1.Either.isLeft(cleared)) {
1555
+ yield* writeStderr((0, index_ts_5.t)("cli.exp.sharedStateRecoveryAlreadyReleasedRegistrationFailed", {
1556
+ key: begun.right.record.key,
1557
+ message: errorMessage(cleared.left),
1558
+ }));
1559
+ return 1;
1560
+ }
1561
+ yield* writeStderr((0, index_ts_5.t)("cli.exp.sharedStateRecoveryAlreadyReleased", { key: begun.right.record.key }));
1562
+ yield* writeStderr((0, index_ts_5.t)("runner.sharedStateExplicitRecovered", {
1563
+ key: begun.right.record.key,
1564
+ experimentId: begun.right.record.experimentId,
1565
+ ownerToken: begun.right.record.ownerToken,
1566
+ }));
1567
+ return 0;
1568
+ }
1569
+ if (begun.right.record.experimentId !== target.experiment.id) {
1570
+ yield* writeStderr(`sharedState key ${JSON.stringify(flags.recoverSharedState)} changed Experiment ownership during recovery; refusing teardown.\n`);
1571
+ return 1;
1572
+ }
1573
+ const registrationWasPresent = yield* effect_1.Effect.either(inspectExactRecoveryTeardownRegistrationEffect({
1574
+ niceevalRoot: niceevalRootForTeardown,
1575
+ record: begun.right.record,
1576
+ currentHost: facts.hostname,
1577
+ }));
1578
+ if (effect_1.Either.isLeft(registrationWasPresent)) {
1579
+ yield* writeStderr((0, index_ts_5.t)("cli.exp.sharedStateRecoveryRegistrationFailed", {
1580
+ key: begun.right.record.key,
1581
+ message: errorMessage(registrationWasPresent.left),
1582
+ }));
1583
+ return 1;
1584
+ }
1585
+ explicitRecovery = {
1586
+ key: begun.right.record.key,
1587
+ ownerToken: begun.right.record.ownerToken,
1588
+ recoveryId: begun.right.recoveryId,
1589
+ experimentId: begun.right.record.experimentId,
1590
+ record: begun.right.record,
1591
+ registrationWasPresent: registrationWasPresent.right,
1592
+ };
1593
+ selectedForTeardown = [target];
1594
+ }
1595
+ else if (hasSharedStateRecoveryOptions(flags)) {
1596
+ yield* writeStderr((0, index_ts_5.t)("cli.exp.sharedStateRecoveryFlags"));
1597
+ return 1;
1598
+ }
1599
+ let anyFailed = false;
1600
+ for (const { experiment, selectedEvalIds } of selectedForTeardown) {
1601
+ const teardown = experimentTeardownOf(experiment);
1602
+ if (teardown === undefined) {
1603
+ // Discovery normally reaches this branch only for an absent
1604
+ // teardown, because defineExperiment rejects non-functions. Keep
1605
+ // the CLI boundary equally strict for dynamically supplied
1606
+ // definitions: a truthy or null-ish impostor must never be
1607
+ // treated as a completed recovery hook.
1608
+ if (experiment.teardown !== undefined) {
1609
+ anyFailed = true;
1610
+ yield* writeStderr((0, index_ts_5.t)("define.experimentTeardownNotFunction"));
1611
+ }
1612
+ continue;
1613
+ }
1614
+ const ctx = {
1615
+ experimentId: experiment.id,
1616
+ selectedEvalIds,
1617
+ signal: new AbortController().signal,
1618
+ progress: () => { },
1619
+ diagnostic: (input) => cliOutput.writeStderrSync(`${input.message}\n`),
1620
+ };
1621
+ if (explicitRecovery !== undefined) {
1622
+ // The exact registration was inspected after the immutable
1623
+ // recovery marker was claimed. Do not re-scan by current
1624
+ // Experiment identity: its sharedState declaration can have
1625
+ // changed, and broad scans could claim another run's cleanup.
1626
+ const outcome = yield* effect_1.Effect.either(cliEffect("run experiment teardown", (0, cleanup_timeout_ts_1.cleanupCallback)(() => teardown(ctx))));
1627
+ if (effect_1.Either.isRight(outcome)) {
1628
+ yield* writeStderr((0, index_ts_5.t)("cli.exp.teardownDone", { experimentId: experiment.id }));
1629
+ }
1630
+ else {
1631
+ anyFailed = true;
1632
+ const error = outcome.left.cause;
1633
+ yield* writeStderr((0, index_ts_5.t)("cli.exp.teardownFailed", {
1634
+ experimentId: experiment.id,
1635
+ message: error instanceof Error ? error.message : String(error),
1636
+ }));
1637
+ }
1638
+ continue;
1639
+ }
1640
+ const registrations = yield* (0, teardown_registry_ts_1.readTeardownRegistrationsEffect)(niceevalRootForTeardown).pipe(effect_1.Effect.catchAll(() => effect_1.Effect.succeed([])));
1641
+ const matching = registrations.filter(({ entry }) => entry.experimentId === experiment.id);
1642
+ const orphaned = matching.filter(({ entry }) => (0, teardown_registry_ts_1.isOrphanedTeardownRegistration)(entry, facts.hostname));
1643
+ const executions = effect_1.Effect.all(orphaned.map(({ id }) => (0, teardown_registry_ts_1.removeTeardownRegistrationIfPresentEffect)(niceevalRootForTeardown, id).pipe(effect_1.Effect.catchAll(() => effect_1.Effect.succeed(false)), effect_1.Effect.map((claimed) => claimed ? id : undefined))), { concurrency: "unbounded" }).pipe(effect_1.Effect.map((claimed) => matching.length === 0 ? [undefined] : claimed.filter((id) => id !== undefined)));
1644
+ for (const _ of yield* executions) {
1645
+ const outcome = yield* effect_1.Effect.either(cliEffect("run experiment teardown", (0, cleanup_timeout_ts_1.cleanupCallback)(() => teardown(ctx))));
1646
+ if (effect_1.Either.isRight(outcome)) {
1647
+ yield* writeStderr((0, index_ts_5.t)("cli.exp.teardownDone", { experimentId: experiment.id }));
1648
+ }
1649
+ else {
1650
+ anyFailed = true;
1651
+ const error = outcome.left.cause;
1652
+ yield* writeStderr((0, index_ts_5.t)("cli.exp.teardownFailed", {
1653
+ experimentId: experiment.id,
1654
+ message: error instanceof Error ? error.message : String(error),
1655
+ }));
1656
+ }
1657
+ }
1658
+ }
1659
+ if (!anyFailed && explicitRecovery !== undefined) {
1660
+ // A waiter may enter as soon as `complete...` publishes free. The
1661
+ // old durable teardown obligation must therefore be removed first.
1662
+ // Any read/claim failure leaves the immutable recovery marker
1663
+ // closed, so an operator can inspect and retry instead of replaying
1664
+ // old teardown after a new owner starts.
1665
+ const cleared = yield* effect_1.Effect.either(clearExactRecoveryTeardownRegistrationEffect({
1666
+ niceevalRoot: niceevalRootForTeardown,
1667
+ record: explicitRecovery.record,
1668
+ currentHost: facts.hostname,
1669
+ expectedRegistration: explicitRecovery.registrationWasPresent,
1670
+ }));
1671
+ if (effect_1.Either.isLeft(cleared)) {
1672
+ yield* writeStderr((0, index_ts_5.t)("cli.exp.sharedStateRecoveryRegistrationFailed", {
1673
+ key: explicitRecovery.key,
1674
+ message: errorMessage(cleared.left),
1675
+ }));
1676
+ return 1;
1677
+ }
1678
+ const completed = yield* effect_1.Effect.either((0, shared_state_lease_ts_1.completeExplicitSharedStateRecoveryEffect)({
1679
+ niceevalRoot: niceevalRootForTeardown,
1680
+ key: explicitRecovery.key,
1681
+ ownerToken: explicitRecovery.ownerToken,
1682
+ recoveryId: explicitRecovery.recoveryId,
1683
+ localHost: facts.hostname,
1684
+ }));
1685
+ if (effect_1.Either.isLeft(completed)) {
1686
+ yield* writeStderr(`sharedState recovery could not release its exact owner token: ${errorMessage(completed.left)}\n`);
1687
+ return 1;
1688
+ }
1689
+ yield* writeStderr((0, index_ts_5.t)("runner.sharedStateExplicitRecovered", {
1690
+ key: explicitRecovery.key,
1691
+ experimentId: explicitRecovery.experimentId,
1692
+ ownerToken: explicitRecovery.ownerToken,
1693
+ }));
1694
+ }
1695
+ return anyFailed ? 1 : 0;
1696
+ }
1697
+ const experimentScopeIds = new Set();
1698
+ for (const { experiment, selectedEvalIds, selectorEvalIds } of selected) {
1699
+ for (const evalId of selectorEvalIds)
1700
+ experimentScopeIds.add(evalId);
1701
+ agentRuns.push(agentRunFromExperiment(experiment, selectedEvalIds, flags));
1702
+ }
1703
+ for (const pattern of extraPatterns) {
1704
+ const matches = (0, aggregate_ts_1.evalPrefixPredicate)([pattern]);
1705
+ if ([...experimentScopeIds].some((id) => matches(id)))
1706
+ continue;
1707
+ yield* writeStderr((0, index_ts_5.t)("cli.experiment.noEvalPrefixMatch", {
1708
+ pattern,
1709
+ selection: expArg ?? (0, index_ts_5.t)("cli.all"),
1710
+ }));
1711
+ return 1;
1712
+ }
1713
+ }
1714
+ else {
1715
+ const listed = yield* cliEffect("list experiments", index_ts_1.experimentHost.list({
1716
+ cwd,
1717
+ ...(flags.tag === undefined ? {} : { tag: flags.tag }),
1718
+ }));
1719
+ const selectedByPattern = yield* effect_1.Effect.all(positionals.map((selector) => cliEffect("select experiments", index_ts_1.experimentHost.list({
1720
+ cwd,
1721
+ ...(flags.tag === undefined ? {} : { tag: flags.tag }),
1722
+ selector,
1723
+ }))));
1724
+ const asExp = [...new Map(selectedByPattern
1725
+ .flatMap((selection) => selection.selections)
1726
+ .map(({ experiment }) => [experiment.id, experiment])).values()];
1727
+ yield* writeStderr((0, index_ts_5.t)("cli.run.experimentRequired"));
1728
+ if (asExp.length > 0) {
1729
+ yield* writeStderr((0, index_ts_5.t)("cli.run.experimentRequiredHint", {
1730
+ pattern: positionals[0] ?? "",
1731
+ kind: asExp.length > 1 ? (0, index_ts_5.t)("cli.experimentGroup") : "",
1732
+ }));
1733
+ }
1734
+ else {
1735
+ yield* writeStderr((0, index_ts_5.t)("cli.run.experimentRequiredKnown", {
1736
+ experiments: listed.experimentIds.join(", ") || (0, index_ts_5.t)("cli.none"),
1737
+ }));
1738
+ }
1739
+ return 1;
1740
+ }
1741
+ const outputForm = (0, index_ts_4.resolveOutputForm)({ json: flags.json, isTTY: facts.stderr.isTTY });
1742
+ const matchedByRun = agentRuns.map((run) => (0, eval_selection_ts_1.selectedEvalsForRun)(evals, run));
1743
+ const totalAttempts = agentRuns.reduce((sum, run, index) => sum + matchedByRun[index].length * run.attempts, 0);
1744
+ const uniqueEvalIds = new Set(matchedByRun.flat().map((evalDefinition) => evalDefinition.id));
1745
+ if (totalAttempts === 0) {
1746
+ yield* writeStderr((0, index_ts_5.t)("cli.experiment.noEvalsSelected", {
1747
+ selection: experimentSelection,
1748
+ experiments: availableExperimentPaths,
1749
+ }));
1750
+ return 1;
1751
+ }
1752
+ if (command === "check") {
1753
+ yield* cliEffect("link run sandboxes", (0, sandbox_selection_ts_1.linkRunSandboxes)(evals, agentRuns));
1754
+ const pairCount = matchedByRun.reduce((sum, selected) => sum + selected.length, 0);
1755
+ yield* writeStdout(`Sandbox layers linked: ${pairCount} pair${pairCount === 1 ? "" : "s"}.\n`);
1756
+ return 0;
1757
+ }
1758
+ const recordTarget = yield* effect_1.Effect.try({
1759
+ try: () => parseActualRecordRoot(path, cwd, flags.record),
1760
+ catch: (cause) => cliFailure("parse experiment Record root", cause),
1761
+ });
1762
+ const experimentPlan = yield* cliEffect("plan Experiment", index_ts_1.experimentHost.plan({
1763
+ evals,
1764
+ agentRuns,
1765
+ config,
1766
+ recordRoot: recordTarget.root,
1767
+ ...(flags.rerun === undefined ? {} : { rerun: flags.rerun }),
1768
+ ...(flags.keepSandbox === undefined ? {} : { keepSandbox: flags.keepSandbox }),
1769
+ ...(flags.dry ? { previewStartedAt: Date.now() } : {}),
1770
+ }));
1771
+ const targetPlan = experimentPlan.target;
1772
+ if (flags.dry) {
1773
+ const current = experimentPlan.current;
1774
+ if (current === undefined) {
1775
+ return yield* effect_1.Effect.fail(cliFailure("preview Experiment", new Error("Experiment Host did not return the requested dry plan.")));
1776
+ }
1777
+ const currentPlan = projectCurrentDryPlan(current);
1778
+ const rows = currentDryPlanRows(currentPlan);
1779
+ const now = Date.now();
1780
+ const lockedFlags = yield* effect_1.Effect.all(rows.map((row) => (0, lock_ts_1.readCaseLockEffect)(coordinationRoot, row.experimentId, row.evalId).pipe(effect_1.Effect.catchAll(() => effect_1.Effect.succeed(undefined)), effect_1.Effect.map((lock) => lock !== undefined && !(0, lock_ts_1.isCaseLockExpired)(lock, now)))), { concurrency: "unbounded" });
1781
+ const evalGroupsByEvalId = new Map(evals.flatMap((evalDef) => evalDef.evalGroup === undefined ? [] : [[evalDef.id, {
1782
+ ...evalDef.evalGroup,
1783
+ index: evalDef.evalGroup.evalIds.indexOf(evalDef.id),
1784
+ }]]));
1785
+ const rowsWithLocks = Object.freeze(rows.map((row, index) => {
1786
+ const evalGroup = evalGroupsByEvalId.get(row.evalId);
1787
+ return Object.freeze({
1788
+ ...row,
1789
+ ...(evalGroup === undefined ? {} : {
1790
+ evalGroupId: evalGroup.id,
1791
+ evalGroupIndex: evalGroup.index,
1792
+ }),
1793
+ ...(lockedFlags[index] ? { locked: true } : {}),
1794
+ });
1795
+ }));
1796
+ const occurrenceAudits = new Map();
1797
+ for (const pair of targetPlan.preparedPairsByKey.values()) {
1798
+ for (const occurrence of pair.plugin.occurrences) {
1799
+ occurrenceAudits.set(JSON.stringify(occurrence.audit), occurrence.audit);
1800
+ }
1801
+ }
1802
+ const pluginAudit = Object.freeze({
1803
+ occurrences: Object.freeze([...occurrenceAudits.values()]),
1804
+ });
1805
+ const input = {
1806
+ totalAttempts,
1807
+ evals: uniqueEvalIds.size,
1808
+ configs: agentRuns.length,
1809
+ attempts: Math.max(1, ...agentRuns.map((run) => run.attempts)),
1810
+ rows: rowsWithLocks,
1811
+ pluginAudit,
1812
+ };
1813
+ if (outputForm === "json") {
1814
+ yield* writeStdout(renderCurrentDryPlanJson(input));
1815
+ }
1816
+ else {
1817
+ yield* writeStdout(renderCurrentDryPlan(input));
1818
+ }
1819
+ return 0;
1820
+ }
1821
+ const maxConcurrency = flags.maxConcurrency ?? config.maxConcurrency ?? (0, sandbox_selection_ts_1.recommendedConcurrencyForPreparedPairs)([...targetPlan.preparedPairsByKey.values()]);
1822
+ const experimentConcurrency = {};
1823
+ for (const run of agentRuns) {
1824
+ if (run.experimentId !== undefined && run.maxConcurrency !== undefined)
1825
+ experimentConcurrency[run.experimentId] = run.maxConcurrency;
1826
+ }
1827
+ const plan = {
1828
+ shape: { evals: uniqueEvalIds.size, configs: agentRuns.length, totalAttempts, maxConcurrency },
1829
+ ...(Object.keys(experimentConcurrency).length === 0 ? {} : { experimentConcurrency }),
1830
+ };
1831
+ const io = terminal.feedback;
1832
+ const commandLabel = ["niceeval", command, ...positionals].join(" ").trim();
1833
+ const renderer = outputForm === "human"
1834
+ ? (0, index_ts_4.createHumanRenderer)({ io, command: commandLabel })
1835
+ : (0, index_ts_4.createJsonRenderer)({ io });
1836
+ const sessionTracker = new session_ts_1.SessionTracker(coordinationRoot);
1837
+ const coordinator = (0, index_ts_4.createFeedbackCoordinator)({
1838
+ profile: outputForm,
1839
+ renderer,
1840
+ io,
1841
+ onEvent: (event, state) => sessionTracker.onFeedback(event, state),
1842
+ });
1843
+ let resourcesReleased = false;
1844
+ const releaseResources = effect_1.Effect.suspend(() => {
1845
+ if (resourcesReleased)
1846
+ return effect_1.Effect.void;
1847
+ resourcesReleased = true;
1848
+ return releaseCliResources();
1849
+ });
1850
+ let sessionClosed = false;
1851
+ const closeSession = (input) => sessionTracker.close(input).pipe(effect_1.Effect.tap(() => effect_1.Effect.sync(() => { sessionClosed = true; })));
1852
+ yield* effect_1.Effect.addFinalizer(() => releaseResources);
1853
+ yield* effect_1.Effect.addFinalizer(() => sessionClosed
1854
+ ? effect_1.Effect.void
1855
+ : closeSession({ status: "incomplete" }).pipe(effect_1.Effect.ignore));
1856
+ yield* effect_1.Effect.addFinalizer(() => cliPromise("stop dynamic feedback", () => coordinator.stopDynamic()).pipe(effect_1.Effect.ignore));
1857
+ yield* effect_1.Effect.acquireRelease(effect_1.Effect.sync(() => (0, index_ts_4.createInputGuard)({
1858
+ stdin: terminal.stdin,
1859
+ stderrIsTTY: io.stderr.isTTY,
1860
+ coordinator,
1861
+ onInterrupt: () => interruption?.requestInterrupt(),
1862
+ })), (inputGuard) => effect_1.Effect.sync(() => inputGuard.stop()));
1863
+ let invocationSummary;
1864
+ const reporters = [];
1865
+ if (flags.junit)
1866
+ reporters.push({ reporter: (0, json_ts_1.JUnit)(flags.junit), name: "junit", required: true, target: flags.junit });
1867
+ (config.reporters ?? []).forEach((reporter, index) => {
1868
+ reporters.push({ reporter, name: `config-reporter-${index}`, required: false });
1869
+ });
1870
+ reporters.push({
1871
+ name: "cli-summary",
1872
+ required: false,
1873
+ reporter: {
1874
+ onInvocationComplete(summary) {
1875
+ invocationSummary = summary;
1876
+ },
1877
+ },
1878
+ });
1879
+ // This synchronous hand-off and the following Experiment Host yield have no
1880
+ // asynchronous gap. A first signal therefore either interrupts the root
1881
+ // before dispatch begins, or aborts this Invocation so it can close its
1882
+ // durable interrupted receipt before the CLI returns.
1883
+ const ownsGracefulDispatch = yield* effect_1.Effect.sync(() => interruption?.enterGracefulDispatch() ?? true);
1884
+ if (!ownsGracefulDispatch)
1885
+ yield* effect_1.Effect.interrupt;
1886
+ // Runner remains Effect-native. During graceful dispatch the application
1887
+ // edge translates SIGINT into this Invocation signal, letting dispatch
1888
+ // settle and the receipt close before the process exits with 130.
1889
+ const receipt = yield* cliEffect("run Experiment", index_ts_1.experimentHost.run({
1890
+ config,
1891
+ evals,
1892
+ agentRuns,
1893
+ reporters,
1894
+ maxConcurrency,
1895
+ maxBuildConcurrency,
1896
+ keepSandbox: flags.keepSandbox,
1897
+ rerun: flags.rerun,
1898
+ coordinationRoot,
1899
+ recordRoot: recordTarget.root,
1900
+ session: sessionTracker,
1901
+ onCurrentRecordReusePlan: (current) => effect_1.Effect.sync(() => coordinator.start({
1902
+ ...plan,
1903
+ shape: { ...plan.shape, runIds: current.runIds },
1904
+ reused: current.reused,
1905
+ ...(current.reusedFailures.length === 0 ? {} : { reusedFailures: current.reusedFailures }),
1906
+ })),
1907
+ ...(interruption === undefined ? {} : { signal: interruption.invocationSignal }),
1908
+ }));
1909
+ yield* releaseResources;
1910
+ const summary = invocationSummary;
1911
+ if (summary === undefined) {
1912
+ return yield* effect_1.Effect.fail(cliFailure("collect invocation summary", new Error("Runner completed without an invocation summary.")));
1913
+ }
1914
+ const completion = assembleInvocationCompletion(coordinator.state);
1915
+ yield* closeSession({ status: completion.status, completion, receipt, completedAt: receipt.completedAt });
1916
+ yield* cliPromise("finish feedback", () => coordinator.finish({ summary, completion, receipt }));
1917
+ const foldedStats = foldInvocationEvalStats(summary);
1918
+ return (0, index_ts_4.computeExitCode)({ ...summary, failed: foldedStats.failed, errored: foldedStats.errored }, completion);
1919
+ });
1920
+ }
1921
+ /** Resolve the CLI's one portable Record target without treating `.niceeval` as Record data. */
1922
+ function parseActualRecordRoot(path, cwd, rootText) {
1923
+ if (rootText !== undefined && rootText.trim() === "") {
1924
+ throw usageError("--record requires an actual Record root directory.\n");
1925
+ }
1926
+ const rootPath = path.resolve(cwd, rootText ?? ".niceeval/record");
1927
+ const root = (0, index_ts_2.makeRecordRoot)(rootPath);
1928
+ if (effect_1.Either.isLeft(root)) {
1929
+ throw usageError(`Invalid --record root: ${root.left.code}.\n`);
1930
+ }
1931
+ return Object.freeze({ root: root.right, rootPath });
1932
+ }
1933
+ /**
1934
+ * The CLI validates selection identity before Record I/O. It never turns a
1935
+ * prefix, a path, or a directory entry into a Run / Experiment selection.
1936
+ */
1937
+ function parseReportCliRequest(input) {
1938
+ const unsupported = reportUnsupportedFlag(input.command, input.flags);
1939
+ if (unsupported !== undefined) {
1940
+ throw usageError(`niceeval ${input.command} does not accept ${unsupported}.\n`);
1941
+ }
1942
+ const runs = input.flags.run ?? [];
1943
+ const { root, rootPath } = parseActualRecordRoot(input.path, input.cwd, input.flags.record);
1944
+ const page = parseReportRoute(input.flags.page);
1945
+ const evidenceReports = [
1946
+ input.flags.source !== undefined ? "--source" : undefined,
1947
+ input.flags.execution ? "--execution" : undefined,
1948
+ input.flags.timing !== undefined ? "--timing" : undefined,
1949
+ ].filter((flag) => flag !== undefined);
1950
+ if (evidenceReports.length > 1) {
1951
+ throw usageError(`niceeval show chooses one evidence Report at a time; remove all but one of ${evidenceReports.join(", ")}.\n`);
1952
+ }
1953
+ if (!input.flags.execution && input.flags.grep !== undefined) {
1954
+ throw usageError("niceeval show --grep only combines with --execution.\n");
1955
+ }
1956
+ if (input.flags.execution) {
1957
+ const report = (0, execution_ts_1.executionEvidenceReport)(executionEvidenceOptions(input.flags));
1958
+ if (input.positionals.length !== 1) {
1959
+ throw usageError("niceeval show --execution requires exactly one current Record Attempt locator.\n");
1960
+ }
1961
+ if (runs.length > 0 || input.flags.experiment !== undefined) {
1962
+ throw usageError("niceeval show --execution uses its Attempt locator as the only selection; remove --run and --experiment.\n");
1963
+ }
1964
+ if (input.flags.report !== undefined) {
1965
+ throw usageError("niceeval show --execution selects its built-in execution Report; remove --report.\n");
1966
+ }
1967
+ const locator = input.positionals[0];
1968
+ if (locator === undefined) {
1969
+ throw usageError("niceeval show --execution requires one current Record Attempt locator.\n");
1970
+ }
1971
+ const parsedLocator = parseCurrentAttemptLocator(locator);
1972
+ return Object.freeze({
1973
+ command: input.command,
1974
+ cwd: input.cwd,
1975
+ root,
1976
+ rootPath,
1977
+ target: Object.freeze({ kind: "attempt", locator: parsedLocator }),
1978
+ reportSelection: Object.freeze({ kind: "fixed", report }),
1979
+ themeSelection: themeSelection(input.path, input.reportPlatform, input.cwd, input.flags.theme),
1980
+ ...(page === undefined ? {} : { page }),
1981
+ });
1982
+ }
1983
+ if (input.flags.timing !== undefined) {
1984
+ if (input.positionals.length !== 1) {
1985
+ throw usageError("niceeval show --timing requires exactly one current Record Attempt locator.\n");
1986
+ }
1987
+ if (runs.length > 0 || input.flags.experiment !== undefined) {
1988
+ throw usageError("niceeval show --timing uses its Attempt locator as the only selection; remove --run and --experiment.\n");
1989
+ }
1990
+ if (input.flags.report !== undefined) {
1991
+ throw usageError("niceeval show --timing selects its built-in timing Report; remove --report.\n");
1992
+ }
1993
+ const locator = input.positionals[0];
1994
+ if (locator === undefined) {
1995
+ throw usageError("niceeval show --timing requires one current Record Attempt locator.\n");
1996
+ }
1997
+ const parsedLocator = parseCurrentAttemptLocator(locator);
1998
+ return Object.freeze({
1999
+ command: input.command,
2000
+ cwd: input.cwd,
2001
+ root,
2002
+ rootPath,
2003
+ target: Object.freeze({ kind: "attempt", locator: parsedLocator }),
2004
+ reportSelection: Object.freeze({
2005
+ kind: "fixed",
2006
+ report: (0, execution_ts_1.timingEvidenceReport)({ mode: input.flags.timing }),
2007
+ }),
2008
+ themeSelection: themeSelection(input.path, input.reportPlatform, input.cwd, input.flags.theme),
2009
+ ...(page === undefined ? {} : { page }),
2010
+ });
2011
+ }
2012
+ if (input.flags.source !== undefined) {
2013
+ if (input.positionals.length !== 1) {
2014
+ throw usageError("niceeval show --source requires exactly one current Record Attempt locator.\n");
2015
+ }
2016
+ if (runs.length > 0 || input.flags.experiment !== undefined) {
2017
+ throw usageError("niceeval show --source uses its Attempt locator as the only selection; remove --run and --experiment.\n");
2018
+ }
2019
+ if (input.flags.report !== undefined) {
2020
+ throw usageError("niceeval show --source selects its built-in source Report; remove --report.\n");
2021
+ }
2022
+ const locator = input.positionals[0];
2023
+ if (locator === undefined) {
2024
+ throw usageError("niceeval show --source requires one current Record Attempt locator.\n");
2025
+ }
2026
+ const parsedLocator = parseCurrentAttemptLocator(locator);
2027
+ const report = (0, source_ts_1.sourceEvidenceReport)({
2028
+ ...(typeof input.flags.source === "string" ? { file: input.flags.source } : {}),
2029
+ });
2030
+ return Object.freeze({
2031
+ command: input.command,
2032
+ cwd: input.cwd,
2033
+ root,
2034
+ rootPath,
2035
+ target: Object.freeze({ kind: "attempt", locator: parsedLocator }),
2036
+ reportSelection: Object.freeze({ kind: "fixed", report }),
2037
+ themeSelection: themeSelection(input.path, input.reportPlatform, input.cwd, input.flags.theme),
2038
+ ...(page === undefined ? {} : { page }),
2039
+ });
2040
+ }
2041
+ if (input.positionals.length > 0) {
2042
+ if (input.positionals.length !== 1) {
2043
+ throw usageError(`niceeval ${input.command} accepts one exact Attempt locator, the default current-project selection, or --run.\n`);
2044
+ }
2045
+ if (runs.length > 0 || input.flags.experiment !== undefined) {
2046
+ throw usageError(`niceeval ${input.command} uses its Attempt locator as the only selection; remove --run and --experiment.\n`);
2047
+ }
2048
+ const locator = input.positionals[0];
2049
+ if (locator === undefined || !locator.startsWith("@")) {
2050
+ throw usageError(`niceeval ${input.command} selects Record data only with a canonical @1<12-character-body> locator, --run, or no selector for current-project results.\n`);
2051
+ }
2052
+ const parsedLocator = parseCurrentAttemptLocator(locator);
2053
+ return Object.freeze({
2054
+ command: input.command,
2055
+ cwd: input.cwd,
2056
+ root,
2057
+ rootPath,
2058
+ target: Object.freeze({ kind: "attempt", locator: parsedLocator }),
2059
+ reportSelection: input.flags.report === undefined
2060
+ ? Object.freeze({ kind: "fixed", report: attempt_overview_ts_1.defaultAttemptOverviewReport })
2061
+ : reportSelection(input.path, input.reportPlatform, input.cwd, input.flags.report),
2062
+ themeSelection: themeSelection(input.path, input.reportPlatform, input.cwd, input.flags.theme),
2063
+ ...(page === undefined ? {} : { page }),
2064
+ });
2065
+ }
2066
+ if (runs.length > 0 && input.flags.experiment !== undefined) {
2067
+ throw usageError("--experiment narrows the default current-project selection; it cannot combine with explicit --run.\n");
2068
+ }
2069
+ const report = runs.length > 0 && input.flags.report === undefined
2070
+ ? Object.freeze({ kind: "fixed", report: run_membership_overview_ts_1.defaultRunMembershipOverviewReport })
2071
+ : reportSelection(input.path, input.reportPlatform, input.cwd, input.flags.report);
2072
+ const theme = themeSelection(input.path, input.reportPlatform, input.cwd, input.flags.theme);
2073
+ const target = runs.length > 0
2074
+ ? Object.freeze({ kind: "selection", selection: explicitSelection(runs) })
2075
+ : Object.freeze({
2076
+ kind: "project-current",
2077
+ ...(input.flags.experiment === undefined
2078
+ ? {}
2079
+ : { experimentSelectors: uniqueExperimentSelectors(input.flags.experiment) }),
2080
+ });
2081
+ return Object.freeze({
2082
+ command: input.command,
2083
+ cwd: input.cwd,
2084
+ root,
2085
+ rootPath,
2086
+ target,
2087
+ reportSelection: report,
2088
+ themeSelection: theme,
2089
+ ...(page === undefined ? {} : { page }),
2090
+ });
2091
+ }
2092
+ function executionEvidenceOptions(flags) {
2093
+ if (flags.grep === undefined)
2094
+ return Object.freeze({});
2095
+ try {
2096
+ new RegExp(flags.grep);
2097
+ return Object.freeze({ grep: flags.grep });
2098
+ }
2099
+ catch {
2100
+ throw usageError(`--grep "${flags.grep}" is not a valid JavaScript regular expression.\n`);
2101
+ }
2102
+ }
2103
+ function reportUnsupportedFlag(command, flags) {
2104
+ const unsupported = [
2105
+ ["--agent", flags.agent],
2106
+ ["--model", flags.model],
2107
+ ["--attempts", flags.attempts],
2108
+ ["--max-concurrency", flags.maxConcurrency],
2109
+ ["--max-build-concurrency", flags.maxBuildConcurrency],
2110
+ ["--timeout", flags.timeout],
2111
+ ["--budget", flags.budget],
2112
+ ["--tag", flags.tag],
2113
+ ["--junit", flags.junit],
2114
+ ["--json", command === "view" && flags.json],
2115
+ ["--smoke", flags.smoke],
2116
+ ["--dry", flags.dry],
2117
+ ["--force", flags.force],
2118
+ ["--rerun", flags.rerun],
2119
+ ["--early-exit/--no-early-exit", flags.earlyExit],
2120
+ ["--open/--no-open", command === "show" ? flags.open : undefined],
2121
+ ["--out", command === "show" ? flags.out : undefined],
2122
+ ["--port", command === "show" ? flags.port : undefined],
2123
+ ["--host", command === "show" ? flags.host : undefined],
2124
+ ["--source", command === "show" ? undefined : flags.source],
2125
+ ["--execution", command === "show" ? undefined : flags.execution],
2126
+ ["--diff", flags.diff || flags.diffPath !== undefined],
2127
+ ["--grep", command === "show" ? undefined : flags.grep],
2128
+ ["--timing", command === "show" ? undefined : flags.timing],
2129
+ ["--keep-sandbox", flags.keepSandbox],
2130
+ ["--all", flags.all],
2131
+ ["--window", flags.window],
2132
+ ["--path", flags.sandboxPath],
2133
+ ["--leave-running", flags.leaveRunning],
2134
+ ["--history", flags.history],
2135
+ ["--usage", flags.usage],
2136
+ ["--stats", flags.stats],
2137
+ ["--orphans", flags.orphans],
2138
+ ["--teardown", flags.teardown],
2139
+ ["--recover-shared-state", flags.recoverSharedState],
2140
+ ["--owner-token", flags.ownerToken],
2141
+ ["--confirm-owner-terminated", flags.confirmOwnerTerminated],
2142
+ ["--confirm-remote-quiesced", flags.confirmRemoteQuiesced],
2143
+ ["--yes", flags.yes],
2144
+ ];
2145
+ const found = unsupported.find(([, value]) => value !== undefined && value !== false && (!Array.isArray(value) || value.length > 0));
2146
+ return found?.[0];
2147
+ }
2148
+ function parseReportRoute(value) {
2149
+ return value;
2150
+ }
2151
+ function reportSelection(path, platform, cwd, value) {
2152
+ if (value === undefined)
2153
+ return Object.freeze({ kind: "config" });
2154
+ if (value === "standard")
2155
+ return Object.freeze({ kind: "built-in", name: "standard" });
2156
+ if (isTrustedModulePath(path, value)) {
2157
+ return Object.freeze({ kind: "module", path: platform.resolveModulePath(cwd, value) });
2158
+ }
2159
+ throw usageError("--report accepts built-in standard or an explicit trusted module path.\n");
2160
+ }
2161
+ function themeSelection(path, platform, cwd, value) {
2162
+ if (value === undefined)
2163
+ return Object.freeze({ kind: "config" });
2164
+ if (value === "basalt" || value === "chalk") {
2165
+ return Object.freeze({ kind: "built-in", name: value });
2166
+ }
2167
+ if (isTrustedModulePath(path, value)) {
2168
+ return Object.freeze({ kind: "module", path: platform.resolveModulePath(cwd, value) });
2169
+ }
2170
+ throw usageError("--theme accepts built-in basalt/chalk or an explicit trusted module path.\n");
2171
+ }
2172
+ function isTrustedModulePath(path, value) {
2173
+ return value.startsWith("./") || value.startsWith("../") || path.isAbsolute(value);
2174
+ }
2175
+ function explicitSelection(values) {
2176
+ const runIds = uniqueExactRunIds(values);
2177
+ const [first, ...rest] = runIds;
2178
+ if (first === undefined) {
2179
+ throw usageError("niceeval show/view requires one or more --run <run-id> values.\n");
2180
+ }
2181
+ const nonEmptyRunIds = [first, ...rest];
2182
+ return Object.freeze({
2183
+ policy: "explicit-runs",
2184
+ runIds: nonEmptyRunIds,
2185
+ });
2186
+ }
2187
+ function uniqueExactRunIds(values) {
2188
+ const result = [];
2189
+ const seen = new Set();
2190
+ for (const value of values) {
2191
+ const decoded = effect_1.Schema.decodeUnknownEither(index_ts_2.RunIdSchema)(value);
2192
+ if (effect_1.Either.isLeft(decoded)) {
2193
+ throw usageError(`Invalid --run value "${value}": expected one exact portable RunId.\n`);
2194
+ }
2195
+ if (!seen.has(decoded.right)) {
2196
+ seen.add(decoded.right);
2197
+ result.push(decoded.right);
2198
+ }
2199
+ }
2200
+ return Object.freeze(result);
2201
+ }
2202
+ function uniqueExperimentSelectors(values) {
2203
+ const result = [];
2204
+ const seen = new Set();
2205
+ for (const value of values) {
2206
+ const decoded = effect_1.Schema.decodeUnknownEither(identifiers_ts_1.ExperimentIdSchema)(value);
2207
+ if (effect_1.Either.isLeft(decoded)) {
2208
+ throw usageError(`Invalid --experiment value "${value}": expected an Experiment selector.\n`);
2209
+ }
2210
+ if (!seen.has(decoded.right)) {
2211
+ seen.add(decoded.right);
2212
+ result.push(decoded.right);
2213
+ }
2214
+ }
2215
+ return Object.freeze(result);
2216
+ }
2217
+ function selectedExperimentIds(slots) {
2218
+ return Object.freeze([...new Set(slots.map((slot) => slot.experimentId))]
2219
+ .sort((left, right) => Buffer.compare(Buffer.from(left), Buffer.from(right))));
2220
+ }
2221
+ /** Current Record locators use the strict scheme-1 short alias. */
2222
+ function parseCurrentAttemptLocator(value) {
2223
+ const parsed = (0, attempt_locator_ts_1.parseAttemptLocator)(value);
2224
+ if (!parsed.valid) {
2225
+ throw usageError(`Invalid Attempt locator "${value}": expected @1 followed by 12 canonical uppercase Crockford characters.\n`);
2226
+ }
2227
+ return parsed.locator;
2228
+ }
2229
+ /**
2230
+ * Config is deliberately fresh-read for every view rebuild even when CLI flags
2231
+ * override its current Report or Theme. It is both a trusted boundary and a
2232
+ * live input: an invalid config must not leave a stale hidden module graph.
2233
+ */
2234
+ function loadCliReportInputs(request) {
2235
+ return effect_1.Effect.gen(function* () {
2236
+ const path = yield* application_ts_1.CliPath;
2237
+ const reportPlatform = yield* application_ts_1.CliReportPlatform;
2238
+ const config = yield* loadConfig(request.cwd);
2239
+ const projectCurrent = request.target.kind === "project-current"
2240
+ ? yield* cliEffect("load current project target", (0, project_current_ts_1.loadProjectCurrent)(request.cwd, {
2241
+ config,
2242
+ ...(request.target.experimentSelectors === undefined
2243
+ ? {}
2244
+ : { experiments: request.target.experimentSelectors }),
2245
+ freshImport: true,
2246
+ }))
2247
+ : undefined;
2248
+ const configured = yield* cliEffect("load trusted Report config", reportPlatform.loadConfig(request.cwd).pipe(effect_1.Effect.mapError((failure) => failure.cause)));
2249
+ const selectedReport = yield* reportFromSelection(reportPlatform, request.reportSelection, configured.report, configured.watchInputs);
2250
+ const selectedTheme = yield* themeFromSelection(reportPlatform, request.themeSelection, configured.theme);
2251
+ const projectCurrentSelection = projectCurrent === undefined
2252
+ ? undefined
2253
+ : Object.freeze({
2254
+ policy: "project-current",
2255
+ currentSlots: projectCurrent.currentSlots,
2256
+ ...(request.target.kind === "project-current" && request.target.experimentSelectors !== undefined
2257
+ ? { experimentIds: selectedExperimentIds(projectCurrent.currentSlots) }
2258
+ : {}),
2259
+ });
2260
+ const sourceWatchInputs = uniqueWatchInputs(path, [
2261
+ ...(projectCurrent?.watchInputs ?? []),
2262
+ ...configured.watchInputs,
2263
+ ...selectedReport.watchInputs,
2264
+ ...selectedTheme.watchInputs,
2265
+ ]);
2266
+ return Object.freeze({
2267
+ report: selectedReport.value,
2268
+ theme: selectedTheme.value,
2269
+ reportWatchInputs: uniqueWatchInputs(path, selectedReport.watchInputs),
2270
+ sourceWatchInputs,
2271
+ ...(projectCurrentSelection === undefined ? {} : { projectCurrentSelection }),
2272
+ watchInputs: uniqueWatchInputs(path, [
2273
+ request.rootPath,
2274
+ ...sourceWatchInputs,
2275
+ ]),
2276
+ });
2277
+ });
2278
+ }
2279
+ function reportFromSelection(platform, selection, configured, configuredWatchInputs) {
2280
+ switch (selection.kind) {
2281
+ case "fixed":
2282
+ return effect_1.Effect.succeed(Object.freeze({ value: selection.report, watchInputs: Object.freeze([]) }));
2283
+ case "config":
2284
+ return effect_1.Effect.succeed(Object.freeze({
2285
+ value: configured ?? standard_tsx_1.standard,
2286
+ watchInputs: configured === undefined ? Object.freeze([]) : configuredWatchInputs,
2287
+ }));
2288
+ case "built-in":
2289
+ return effect_1.Effect.succeed(Object.freeze({ value: standard_tsx_1.standard, watchInputs: Object.freeze([]) }));
2290
+ case "module":
2291
+ return cliEffect("load trusted Report module", platform.loadReport(selection.path).pipe(effect_1.Effect.mapError((failure) => failure.cause))).pipe(effect_1.Effect.map((loaded) => Object.freeze({ value: loaded.report, watchInputs: loaded.watchInputs })));
2292
+ }
2293
+ }
2294
+ function themeFromSelection(platform, selection, configured) {
2295
+ switch (selection.kind) {
2296
+ case "config":
2297
+ return effect_1.Effect.succeed(Object.freeze({ value: configured ?? theme_ts_1.basalt, watchInputs: Object.freeze([]) }));
2298
+ case "built-in":
2299
+ return effect_1.Effect.succeed(Object.freeze({
2300
+ value: selection.name === "chalk" ? theme_ts_1.chalk : theme_ts_1.basalt,
2301
+ watchInputs: Object.freeze([]),
2302
+ }));
2303
+ case "module":
2304
+ return cliEffect("load trusted Theme module", platform.loadTheme(selection.path).pipe(effect_1.Effect.mapError((failure) => failure.cause))).pipe(effect_1.Effect.map((loaded) => Object.freeze({ value: loaded.theme, watchInputs: loaded.watchInputs })));
2305
+ }
2306
+ }
2307
+ function uniqueWatchInputs(pathService, paths) {
2308
+ return Object.freeze([...new Set(paths.map((path) => pathService.resolve(path)))].sort());
2309
+ }
2310
+ function buildCliReportSite(request, inputs) {
2311
+ const revision = request.target.kind === "attempt"
2312
+ ? (0, from_record_ts_1.buildReportSiteFromRecord)({
2313
+ root: request.root,
2314
+ locator: request.target.locator,
2315
+ report: inputs.report,
2316
+ theme: inputs.theme,
2317
+ })
2318
+ : (0, from_record_ts_1.buildReportSiteFromRecord)({
2319
+ root: request.root,
2320
+ selection: request.target.kind === "selection"
2321
+ ? request.target.selection
2322
+ : inputs.projectCurrentSelection,
2323
+ report: inputs.report,
2324
+ theme: inputs.theme,
2325
+ });
2326
+ return revision.pipe(effect_1.Effect.mapError(reportExecutionFailure));
2327
+ }
2328
+ function reportExecutionFailure(error) {
2329
+ switch (failureCode(error)) {
2330
+ case "sample-run-not-found":
2331
+ return usageError(`Run "${stringProperty(error, "runId") ?? "unknown"}" was not found in the selected Record.\n`);
2332
+ case "sample-run-invalid":
2333
+ return usageError(`Run "${stringProperty(error, "runId") ?? "unknown"}" is not a valid published Record Run.\n`);
2334
+ case "sample-selection-invalid":
2335
+ return usageError(`Invalid Record analysis selection: ${stringProperty(error, "field") ?? "selection"}.\n`);
2336
+ case "sample-attempt-not-found":
2337
+ return usageError(`Attempt "${stringProperty(error, "attemptId") ?? "unknown"}" was not found in the selected Record.\n`);
2338
+ case "sample-attempt-ambiguous":
2339
+ return usageError(`Attempt "${stringProperty(error, "attemptId") ?? "unknown"}" is ambiguous in the selected Record.\n`);
2340
+ case "sample-attempt-locator-not-found":
2341
+ return usageError(`Attempt locator "${stringProperty(error, "locator") ?? "unknown"}" was not found in the selected Record.\n`);
2342
+ case "sample-attempt-locator-ambiguous":
2343
+ return usageError(`Attempt locator "${stringProperty(error, "locator") ?? "unknown"}" is ambiguous in the selected Record.\n`);
2344
+ case "record-migration-required":
2345
+ return usageError("record-migration-required\nRun: niceeval migrate\n");
2346
+ case "record-bootstrap-invalid":
2347
+ return usageError("record-bootstrap-invalid\nPass --record <actual-record-root> or create a current NiceEval Record.\n");
2348
+ case "record-format-unsupported":
2349
+ return usageError("record-format-unsupported\nThis Record was created by a NiceEval version that is no longer supported. Remove .niceeval (for example, rm -rf .niceeval) and rerun the evaluation.\n");
2350
+ case "record-migration-interrupted":
2351
+ return usageError("record-migration-interrupted\nRestore the Record from Git or a backup before retrying.\n");
2352
+ case "report-sample-empty":
2353
+ return usageError("report-sample-empty\nRun an evaluation matching the current project, or select recorded results with --run.\n");
2354
+ case "report-route-invalid":
2355
+ return usageError(`Unknown Report route "${stringProperty(error, "route") ?? "unknown"}".\n`);
2356
+ case "report-build-budget-exceeded":
2357
+ return usageError(reportBuildBudgetExceededMessage(error));
2358
+ case "report-site-execution-problem":
2359
+ return usageError(reportSiteExecutionProblemMessage(error));
2360
+ default:
2361
+ return cliFailure("execute report from Record", error);
2362
+ }
2363
+ }
2364
+ function automaticRecordMigrationFailure(error) {
2365
+ const code = failureCode(error);
2366
+ if (code === "record-auto-migration-git-save-required") {
2367
+ return usageError("record-auto-migration-git-save-required\n" +
2368
+ "Save every portable Record byte first (for example: git add .niceeval/record && git commit), then retry the same command.\n");
2369
+ }
2370
+ if (code === "record-maintenance-busy" || code === "record-writer-busy") {
2371
+ return usageError(`${code}\nClose the command using this Record, then retry.\n`);
2372
+ }
2373
+ if (code === "record-format-unsupported") {
2374
+ return usageError("record-format-unsupported\nUse the NiceEval version that wrote this future or unknown Record format.\n");
2375
+ }
2376
+ if (code === "record-migration-recovery-required" || code === "record-migration-interrupted") {
2377
+ return usageError(`${code}\nRestore and verify the complete Record from Git before retrying.\n`);
2378
+ }
2379
+ return cliFailure("automatically migrate Record", error);
2380
+ }
2381
+ /** Preserve the actionable dimension and target from the Host's closed budget error. */
2382
+ function reportBuildBudgetExceededMessage(error) {
2383
+ const budget = stringProperty(error, "budget");
2384
+ const maximum = finiteNumberProperty(error, "maximum");
2385
+ const observedAtLeast = finiteNumberProperty(error, "observedAtLeast");
2386
+ if (budget === undefined || maximum === undefined || observedAtLeast === undefined) {
2387
+ return "report-build-budget-exceeded\n";
2388
+ }
2389
+ const pageId = stringProperty(error, "pageId");
2390
+ const route = stringProperty(error, "route");
2391
+ const target = pageId === undefined && route === undefined
2392
+ ? ""
2393
+ : ` (${[
2394
+ ...(pageId === undefined ? [] : [`page ${JSON.stringify(pageId)}`]),
2395
+ ...(route === undefined ? [] : [`route ${JSON.stringify(route)}`]),
2396
+ ].join(", ")})`;
2397
+ return `report-build-budget-exceeded: ${budget} observed at least ${observedAtLeast}; maximum ${maximum}${target}\n`;
2398
+ }
2399
+ const REPORT_CLI_PROBLEMS_MAX = 20;
2400
+ const REPORT_CLI_PROBLEM_TEXT_MAX = 1024;
2401
+ /** Keep the concrete closed-problem reason while bounding author-controlled diagnostics. */
2402
+ function reportSiteExecutionProblemMessage(error) {
2403
+ if (typeof error !== "object" || error === null)
2404
+ return "report-site-execution-problem\n";
2405
+ const problems = Reflect.get(error, "problems");
2406
+ if (!Array.isArray(problems))
2407
+ return "report-site-execution-problem\n";
2408
+ const lines = problems
2409
+ .slice(0, REPORT_CLI_PROBLEMS_MAX)
2410
+ .flatMap((problem) => {
2411
+ const code = boundedReportCliProblemText(stringProperty(problem, "code"));
2412
+ const summary = boundedReportCliProblemText(stringProperty(problem, "summary"));
2413
+ if (code === undefined && summary === undefined)
2414
+ return [];
2415
+ if (code === undefined)
2416
+ return [summary];
2417
+ return [summary === undefined ? code : `${code}: ${summary}`];
2418
+ });
2419
+ const omitted = Math.max(0, problems.length - REPORT_CLI_PROBLEMS_MAX);
2420
+ return [
2421
+ "report-site-execution-problem",
2422
+ ...lines,
2423
+ ...(omitted === 0 ? [] : [`${omitted} additional Report problem(s) omitted.`]),
2424
+ "",
2425
+ ].join("\n");
2426
+ }
2427
+ function boundedReportCliProblemText(value) {
2428
+ if (value === undefined)
2429
+ return undefined;
2430
+ const bounded = value
2431
+ .replace(/[\u0000-\u001f\u007f-\u009f]/g, " ")
2432
+ .slice(0, REPORT_CLI_PROBLEM_TEXT_MAX)
2433
+ .trim();
2434
+ return bounded === "" ? undefined : bounded;
2435
+ }
2436
+ function stringProperty(value, key) {
2437
+ if (typeof value !== "object" || value === null)
2438
+ return undefined;
2439
+ const candidate = Reflect.get(value, key);
2440
+ return typeof candidate === "string" ? candidate : undefined;
2441
+ }
2442
+ function finiteNumberProperty(value, key) {
2443
+ if (typeof value !== "object" || value === null)
2444
+ return undefined;
2445
+ const candidate = Reflect.get(value, key);
2446
+ return typeof candidate === "number" && Number.isFinite(candidate) ? candidate : undefined;
2447
+ }
2448
+ function makeCliReportConsole(output) {
2449
+ return Object.freeze({
2450
+ write: (text) => effect_1.Effect.try({
2451
+ try: () => {
2452
+ output.writeStdoutSync(text);
2453
+ },
2454
+ catch: () => Object.freeze({
2455
+ code: "report-console-write-failed",
2456
+ operation: "write",
2457
+ }),
2458
+ }),
2459
+ });
2460
+ }
2461
+ const reportHostPhaseLabels = Object.freeze({
2462
+ "record-open": "Open Record",
2463
+ "selection": "Select runs and attempts",
2464
+ "sample-open": "Open analysis Sample",
2465
+ "report-execution": "Execute Report and read requested facts",
2466
+ });
2467
+ function formatReportProgressDuration(durationMs) {
2468
+ if (durationMs < 1000)
2469
+ return `${Math.round(durationMs)}ms`;
2470
+ return `${(durationMs / 1000).toFixed(1)}s`;
2471
+ }
2472
+ function writeReportProgress(output, text) {
2473
+ try {
2474
+ output.writeStderrSync(text);
2475
+ }
2476
+ catch {
2477
+ // Progress is diagnostic only; it must never alter Report execution.
2478
+ }
2479
+ }
2480
+ const cliProgressFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
2481
+ /**
2482
+ * Keeps interactive progress on one terminal line, while preserving plain
2483
+ * line-oriented diagnostics when stderr is redirected to a file or CI log.
2484
+ */
2485
+ class CliReportProgress {
2486
+ stderrIsTTY;
2487
+ output;
2488
+ active;
2489
+ constructor(stderrIsTTY, output) {
2490
+ this.stderrIsTTY = stderrIsTTY;
2491
+ this.output = output;
2492
+ }
2493
+ start(label) {
2494
+ if (!this.stderrIsTTY) {
2495
+ writeReportProgress(this.output, `⠋ ${label}…\n`);
2496
+ return () => undefined;
2497
+ }
2498
+ if (this.active !== undefined)
2499
+ clearInterval(this.active.timer);
2500
+ let frame = 0;
2501
+ const render = () => writeReportProgress(this.output, `\r\u001b[2K${cliProgressFrames[frame++ % cliProgressFrames.length]} ${label}…`);
2502
+ render();
2503
+ const timer = setInterval(render, 80);
2504
+ const active = { label, timer };
2505
+ this.active = active;
2506
+ return () => {
2507
+ if (this.active !== active)
2508
+ return;
2509
+ clearInterval(timer);
2510
+ this.active = undefined;
2511
+ };
2512
+ }
2513
+ complete(label, text) {
2514
+ if (this.stderrIsTTY) {
2515
+ if (this.active?.label === label) {
2516
+ clearInterval(this.active.timer);
2517
+ this.active = undefined;
2518
+ }
2519
+ writeReportProgress(this.output, `\r\u001b[2K${text}\n`);
2520
+ return;
2521
+ }
2522
+ writeReportProgress(this.output, `${text}\n`);
2523
+ }
2524
+ }
2525
+ function makeCliReportProgress(rootPath, progress) {
2526
+ return Object.freeze({
2527
+ report: (event) => {
2528
+ const label = event.phase === "record-open"
2529
+ ? `${reportHostPhaseLabels[event.phase]} (${rootPath})`
2530
+ : reportHostPhaseLabels[event.phase];
2531
+ if (event.type === "start") {
2532
+ progress.start(label);
2533
+ return;
2534
+ }
2535
+ const marker = event.outcome === "success" ? "✓" : "×";
2536
+ const outcome = event.outcome === "success" ? "" : ` · ${event.outcome}`;
2537
+ progress.complete(label, `${marker} ${label} (${formatReportProgressDuration(event.durationMs)}${outcome})`);
2538
+ },
2539
+ });
2540
+ }
2541
+ function cliPhaseOutcome(exit) {
2542
+ if (effect_1.Exit.isSuccess(exit))
2543
+ return "success";
2544
+ if (effect_1.Cause.isInterruptedOnly(exit.cause))
2545
+ return "interrupted";
2546
+ return effect_1.Cause.defects(exit.cause).pipe((defects) => defects.length > 0) ? "defect" : "failure";
2547
+ }
2548
+ function withCliReportPhase(progress, label, effect) {
2549
+ return effect_1.Effect.gen(function* () {
2550
+ const startedAt = yield* effect_1.Clock.currentTimeNanos;
2551
+ const stopProgress = yield* effect_1.Effect.sync(() => progress.start(label));
2552
+ return yield* effect.pipe(effect_1.Effect.onExit((exit) => effect_1.Effect.gen(function* () {
2553
+ const completedAt = yield* effect_1.Clock.currentTimeNanos;
2554
+ const durationMs = Math.max(0, Number(completedAt - startedAt) / 1000000);
2555
+ const outcome = cliPhaseOutcome(exit);
2556
+ const marker = outcome === "success" ? "✓" : "×";
2557
+ const detail = outcome === "success" ? "" : ` · ${outcome}`;
2558
+ yield* effect_1.Effect.sync(() => {
2559
+ stopProgress();
2560
+ progress.complete(label, `${marker} ${label} (${formatReportProgressDuration(durationMs)}${detail})`);
2561
+ });
2562
+ })));
2563
+ });
2564
+ }
2565
+ function runShowCommand(cwd, positionals, flags) {
2566
+ return effect_1.Effect.gen(function* () {
2567
+ const path = yield* application_ts_1.CliPath;
2568
+ const reportPlatform = yield* application_ts_1.CliReportPlatform;
2569
+ const facts = yield* invocationFacts;
2570
+ const cliOutput = yield* application_ts_1.CliOutput;
2571
+ const progress = new CliReportProgress(facts.stderr.isTTY, cliOutput);
2572
+ const request = yield* effect_1.Effect.try({
2573
+ try: () => parseReportCliRequest({ path, reportPlatform, command: "show", cwd, positionals, flags }),
2574
+ catch: (cause) => cliFailure("parse show arguments", cause),
2575
+ });
2576
+ const inputs = yield* withCliReportPhase(progress, "Load project, config, Report, and Theme", loadCliReportInputs(request));
2577
+ const textProjection = flags.json
2578
+ ? undefined
2579
+ : (0, panel_ts_1.panelCapabilityOf)({
2580
+ isTTY: facts.stdout.isTTY,
2581
+ width: facts.stdout.columns,
2582
+ });
2583
+ const show = request.target.kind === "attempt"
2584
+ ? index_ts_3.reportHost.show({
2585
+ root: request.root,
2586
+ locator: request.target.locator,
2587
+ report: inputs.report,
2588
+ ...(request.page === undefined ? {} : { route: request.page }),
2589
+ ...(textProjection === undefined ? {} : { textProjection: { width: textProjection.width, panelMode: textProjection.mode } }),
2590
+ format: flags.json ? "json" : "text",
2591
+ })
2592
+ : index_ts_3.reportHost.show({
2593
+ root: request.root,
2594
+ selection: request.target.kind === "selection"
2595
+ ? request.target.selection
2596
+ : inputs.projectCurrentSelection,
2597
+ report: inputs.report,
2598
+ ...(request.page === undefined ? {} : { route: request.page }),
2599
+ ...(textProjection === undefined ? {} : { textProjection: { width: textProjection.width, panelMode: textProjection.mode } }),
2600
+ format: flags.json ? "json" : "text",
2601
+ });
2602
+ const output = yield* show.pipe(effect_1.Effect.provideService(progress_ts_1.ReportHostProgressObserver, makeCliReportProgress(request.rootPath, progress)), effect_1.Effect.mapError(reportExecutionFailure));
2603
+ yield* makeCliReportConsole(cliOutput).write(output).pipe(effect_1.Effect.mapError((error) => cliFailure("render Report show output", error)));
2604
+ return 0;
2605
+ });
2606
+ }
2607
+ function runViewCommand(cwd, positionals, flags) {
2608
+ return effect_1.Effect.gen(function* () {
2609
+ const path = yield* application_ts_1.CliPath;
2610
+ const reportPlatform = yield* application_ts_1.CliReportPlatform;
2611
+ const facts = yield* invocationFacts;
2612
+ const cliOutput = yield* application_ts_1.CliOutput;
2613
+ const progress = new CliReportProgress(facts.stderr.isTTY, cliOutput);
2614
+ const request = yield* effect_1.Effect.try({
2615
+ try: () => parseReportCliRequest({ path, reportPlatform, command: "view", cwd, positionals, flags }),
2616
+ catch: (cause) => cliFailure("parse view arguments", cause),
2617
+ });
2618
+ if (flags.out !== undefined) {
2619
+ if (flags.out.trim() === "") {
2620
+ return yield* effect_1.Effect.fail(usageError("--out requires a target directory.\n"));
2621
+ }
2622
+ if (flags.port !== undefined || flags.host !== undefined || flags.open === true) {
2623
+ return yield* effect_1.Effect.fail(usageError("view --out does not start a server; remove --port, --host, and --open.\n"));
2624
+ }
2625
+ if (request.page !== undefined) {
2626
+ return yield* effect_1.Effect.fail(usageError("view --out does not accept --page.\n"));
2627
+ }
2628
+ const inputs = yield* withCliReportPhase(progress, "Load project, config, Report, and Theme", loadCliReportInputs(request));
2629
+ const revision = yield* buildCliReportSite(request, inputs).pipe(effect_1.Effect.provideService(progress_ts_1.ReportHostProgressObserver, makeCliReportProgress(request.rootPath, progress)));
2630
+ const receipt = yield* withCliReportPhase(progress, "Export static report site", index_ts_3.reportHost.export({
2631
+ revision,
2632
+ out: path.resolve(cwd, flags.out),
2633
+ }).pipe(effect_1.Effect.mapError((error) => staticExportFailure(error, path.resolve(cwd, flags.out)))));
2634
+ yield* writeStdout(`Exported static report site: ${receipt.out}\n`);
2635
+ return 0;
2636
+ }
2637
+ const initialInputs = yield* withCliReportPhase(progress, "Load project, config, Report, and Theme", loadCliReportInputs(request));
2638
+ const initial = yield* buildCliReportSite(request, initialInputs).pipe(effect_1.Effect.provideService(progress_ts_1.ReportHostProgressObserver, makeCliReportProgress(request.rootPath, progress)));
2639
+ const buildCache = yield* makeReportViewBuildCache(request, initialInputs, initial);
2640
+ const { host, port } = yield* effect_1.Effect.try({
2641
+ try: () => ({ host: viewHost(flags.host), port: viewPort(flags.port) }),
2642
+ catch: (cause) => cliFailure("parse view server arguments", cause),
2643
+ });
2644
+ const server = yield* withCliReportPhase(progress, "Start HTTP server and file watcher", index_ts_3.reportHost.serve({
2645
+ url: `http://${host.includes(":") ? `[${host}]` : host}:${port}/`,
2646
+ watchInputs: initialInputs.watchInputs,
2647
+ initial: effect_1.Effect.succeed(initial),
2648
+ rebuild: () => rebuildReportView(request, buildCache),
2649
+ host,
2650
+ port,
2651
+ }).pipe(effect_1.Effect.mapError((error) => cliFailure("open report view", error))));
2652
+ const urls = server.urls.map((url) => {
2653
+ if (request.page === undefined)
2654
+ return url;
2655
+ const target = new URL(url);
2656
+ target.hash = `#${request.page}`;
2657
+ return target.toString();
2658
+ });
2659
+ const url = urls[0];
2660
+ if (!isLoopbackViewHost(host)) {
2661
+ yield* writeStderr("Warning: niceeval view is listening beyond loopback without authentication or TLS; " +
2662
+ "every reachable client can read report data, execution JSON, and downloads.\n");
2663
+ }
2664
+ yield* writeStdout(`niceeval view — open in a browser:\n${urls.join("\n")}\n`);
2665
+ if (flags.open !== false) {
2666
+ yield* openBrowser(url).pipe(effect_1.Effect.catchAll(() => effect_1.Effect.succeed(false)));
2667
+ }
2668
+ return yield* effect_1.Effect.never;
2669
+ });
2670
+ }
2671
+ function rebuildReportView(request, cache) {
2672
+ return effect_1.Effect.gen(function* () {
2673
+ const inputs = yield* loadCliReportInputs(request);
2674
+ const revision = yield* buildCliReportSite(request, inputs);
2675
+ cache.revision = revision;
2676
+ cache.watchInputs = inputs.watchInputs;
2677
+ return Object.freeze({
2678
+ kind: "site",
2679
+ site: revision,
2680
+ watchInputs: inputs.watchInputs,
2681
+ });
2682
+ }).pipe(effect_1.Effect.catchAll((failure) => {
2683
+ const problem = reportViewRebuildFailure(failure);
2684
+ return writeStderr(`view rebuild failed: ${problem.summary}\n`).pipe(effect_1.Effect.ignore, effect_1.Effect.zipRight(effect_1.Effect.fail(problem)));
2685
+ }));
2686
+ }
2687
+ function makeReportViewBuildCache(request, inputs, revision) {
2688
+ void request;
2689
+ return effect_1.Effect.succeed({ revision, watchInputs: inputs.watchInputs });
2690
+ }
2691
+ function staticExportFailure(error, out) {
2692
+ if (failureCode(error) === "report-export-target-exists") {
2693
+ return usageError(`report-export-target-exists\nRemove ${out} before retrying.\n`);
2694
+ }
2695
+ return cliFailure("export static Report", error);
2696
+ }
2697
+ function viewHost(value) {
2698
+ const host = (value ?? "127.0.0.1").trim();
2699
+ if (host.length === 0)
2700
+ throw usageError("--host requires a non-empty address.\n");
2701
+ return host;
2702
+ }
2703
+ function isLoopbackViewHost(host) {
2704
+ return host === "127.0.0.1" || host === "::1" || host === "localhost";
2705
+ }
2706
+ function viewPort(value) {
2707
+ const port = value ?? 0;
2708
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
2709
+ throw usageError(`--port must be an integer from 0 through 65535, got ${port}.\n`);
2710
+ }
2711
+ return port;
2712
+ }
2713
+ function reportViewRebuildFailure(failure) {
2714
+ const summary = failure._tag === "CliUsageError"
2715
+ ? failure.message
2716
+ : failureCode(failure.cause) ?? "Report rebuild failed";
2717
+ return Object.freeze({ summary });
2718
+ }
2719
+ const cliProgram = (interruption) => effect_1.Effect.gen(function* () {
2720
+ const facts = yield* invocationFacts;
2721
+ const cliArguments = yield* application_ts_1.CliArguments;
2722
+ const cwd = facts.cwd;
2723
+ const projectConfiguration = yield* project_configuration_ts_1.ProjectConfiguration;
2724
+ const { command, positionals, flags, providedOptions } = yield* effect_1.Effect.try({
2725
+ try: () => parseArgs([...facts.argv], cliArguments.parse),
2726
+ catch: (cause) => cliFailure("parse CLI arguments", cause),
2727
+ });
2728
+ if (flags.help) {
2729
+ yield* writeStdout((0, index_ts_5.t)("cli.help"));
2730
+ return 0;
2731
+ }
2732
+ if (command === "exp" && positionals.length === 1 && positionals[0] === "help") {
2733
+ yield* writeStdout((0, index_ts_5.t)("cli.help"));
2734
+ return 0;
2735
+ }
2736
+ if (flags.version) {
2737
+ yield* writeStdout((yield* packageVersion()) + "\n");
2738
+ return 0;
2739
+ }
2740
+ // Help/version and Session / Docker profile queries are config-free paths.
2741
+ // In particular, asking for usage must not read or mutate cwd/.env.
2742
+ if (command !== "session" && command !== "docker") {
2743
+ yield* projectConfiguration.prepare(cwd).pipe(effect_1.Effect.mapError((cause) => cliFailure("prepare project credentials", cause)));
2744
+ }
2745
+ if (command === "show" || command === "view" || command === "exp") {
2746
+ const migrated = yield* (0, record_ts_1.ensureAutomaticRecordMigration)({
2747
+ cwd,
2748
+ ...(flags.record === undefined ? {} : { record: flags.record }),
2749
+ }).pipe(effect_1.Effect.mapError(automaticRecordMigrationFailure));
2750
+ if (migrated !== null) {
2751
+ const droppedFacts = [...new Set(migrated.attachments.flatMap((attachment) => attachment.retention.droppedFacts))];
2752
+ const rerunRecommendations = [...new Set(migrated.attachments.flatMap((attachment) => attachment.retention.rerunRecommendation === null
2753
+ ? []
2754
+ : [attachment.retention.rerunRecommendation]))];
2755
+ yield* writeStderr(`Record automatically migrated ${migrated.attachments.length} attachment${migrated.attachments.length === 1 ? "" : "s"}; restore commit ${migrated.restoreCommit}.${droppedFacts.length === 0 ? "" : ` Dropped facts: ${droppedFacts.join(", ")}.`}${rerunRecommendations.length === 0 ? "" : ` ${rerunRecommendations.join(" ")}`}\n`);
2756
+ }
2757
+ }
2758
+ if (command === "debug") {
2759
+ const unsupported = providedOptions.find((name) => name !== "json" && name !== "help" && name !== "version");
2760
+ if (unsupported !== undefined) {
2761
+ yield* writeStderr((0, index_ts_5.t)("cli.debug.flagUnsupported", { flag: `--${unsupported}` }));
2762
+ return 1;
2763
+ }
2764
+ return yield* runDebugCommand(cwd, positionals, flags);
2765
+ }
2766
+ if (command === "docker")
2767
+ return yield* runDockerCommand(positionals, flags);
2768
+ if (command === "accept") {
2769
+ const locators = yield* effect_1.Effect.try({
2770
+ try: () => parseAcceptLocators(positionals, flags),
2771
+ catch: (cause) => cliFailure("parse acceptance locators", cause),
2772
+ });
2773
+ const outcome = yield* effect_1.Effect.either(runAcceptCommand(cwd, locators, flags.record));
2774
+ if (effect_1.Either.isLeft(outcome)) {
2775
+ yield* writeStderr((0, index_ts_5.t)("cli.accept.failed", { error: errorMessage(outcome.left.cause) }));
2776
+ return 1;
2777
+ }
2778
+ return 0;
2779
+ }
2780
+ if (command === "view")
2781
+ return yield* runViewCommand(cwd, positionals, flags);
2782
+ if (command === "sandbox")
2783
+ return yield* runSandboxCliCommand(cwd, positionals, flags);
2784
+ if (command === "session")
2785
+ return yield* runSessionCommand(cwd, positionals, flags);
2786
+ if (command === "show")
2787
+ return yield* runShowCommand(cwd, positionals, flags);
2788
+ if (command === "clean" || command === "migrate") {
2789
+ return yield* runRecordMaintenanceCommand(cwd, command, positionals, flags);
2790
+ }
2791
+ if (command === "init") {
2792
+ const initialized = yield* initProject(cwd);
2793
+ yield* writeStdout((0, index_ts_5.t)("cli.init.done"));
2794
+ if (!initialized.prefersEsm)
2795
+ yield* writeStdout((0, index_ts_5.t)("cli.init.esmHint"));
2796
+ return 0;
2797
+ }
2798
+ if (command === "exp" && positionals[0] === "rename") {
2799
+ return yield* runExperimentRenameCommand(cwd, positionals.slice(1), flags);
2800
+ }
2801
+ return yield* runEvaluationCommand(cwd, command, positionals, flags, interruption);
2802
+ }).pipe(
2803
+ // Every command boundary reports a typed CLI failure; defects and
2804
+ // interruption remain in the Cause channel for bootstrap to own.
2805
+ effect_1.Effect.mapError((cause) => cliFailure("run CLI command", cause)));
2806
+ exports.cliProgram = cliProgram;
2807
+ //# sourceMappingURL=program.cjs.map