reeval 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -64
- package/THIRD_PARTY_NOTICES.md +288 -112
- package/assets/package.json +1 -1
- package/assets/pnpm-lock.yaml +30 -30
- package/dist/adapter-Bwy3QxAK.js +279 -0
- package/dist/adapter-Bwy3QxAK.js.map +1 -0
- package/dist/{agent-6FefIhX7.js → agent-D483ru7D.js} +6 -6
- package/dist/{agent-6FefIhX7.js.map → agent-D483ru7D.js.map} +1 -1
- package/dist/{agent-WSmTaesf.js → agent-DpE80WPz.js} +46 -15
- package/dist/agent-DpE80WPz.js.map +1 -0
- package/dist/{analysis-DlGBDjTj.js → analysis-Bxub9LMT.js} +4 -4
- package/dist/{analysis-DlGBDjTj.js.map → analysis-Bxub9LMT.js.map} +1 -1
- package/dist/assemble-D5KGrwnJ.js +1 -0
- package/dist/{assemble-BCZzTyR7.js → assemble-D9GWr-HS.js} +69 -41
- package/dist/assemble-D9GWr-HS.js.map +1 -0
- package/dist/{client-Bbo9SPlK.js → assert-EUIoRdIt.js} +10 -113
- package/dist/assert-EUIoRdIt.js.map +1 -0
- package/dist/bridge/index.mjs +260 -60
- package/dist/{capture-Bp74Z6Fr.js → capture-BQlGHiMC.js} +146 -32
- package/dist/capture-BQlGHiMC.js.map +1 -0
- package/dist/capture-CYGLpWxR.js +1 -0
- package/dist/{cli-CVrMh_qz.js → cli-ClpPT8kn.js} +229 -537
- package/dist/cli-ClpPT8kn.js.map +1 -0
- package/dist/{cli-spec-CKEYa8V2.js → cli-spec-qmEnQ25_.js} +50 -22
- package/dist/cli-spec-qmEnQ25_.js.map +1 -0
- package/dist/cli.js +4 -2
- package/dist/cli.js.map +1 -1
- package/dist/client-fcwgwOAD.js +106 -0
- package/dist/client-fcwgwOAD.js.map +1 -0
- package/dist/{command-DUT5l13I.js → command-Bj39y-Wm.js} +329 -87
- package/dist/command-Bj39y-Wm.js.map +1 -0
- package/dist/{command-BFP0hKEb.js → command-BlM0y8jN.js} +14 -14
- package/dist/{command-BFP0hKEb.js.map → command-BlM0y8jN.js.map} +1 -1
- package/dist/command-C-cVjlny.js +1 -0
- package/dist/{command-CzIFp1JQ.js → command-DbAe1Rzb.js} +19 -19
- package/dist/command-DbAe1Rzb.js.map +1 -0
- package/dist/{command-C5xFPKHM.js → command-TgbzPizb.js} +378 -74
- package/dist/command-TgbzPizb.js.map +1 -0
- package/dist/commands-CthqwkAn.js +1 -0
- package/dist/{commands-CLmPBrTS.js → commands-YUwlyTbr.js} +14 -11
- package/dist/commands-YUwlyTbr.js.map +1 -0
- package/dist/config-DrVSEVN0.js +774 -0
- package/dist/config-DrVSEVN0.js.map +1 -0
- package/dist/{context-DbSMEVKs.js → context-C1mxXh80.js} +11 -3
- package/dist/context-C1mxXh80.js.map +1 -0
- package/dist/contrasts-5zsj32gk.js +101 -0
- package/dist/contrasts-5zsj32gk.js.map +1 -0
- package/dist/db-CKg5NtiW.js +2 -0
- package/dist/{db-CfQFwJtq.js → db-DfhipHEF.js} +467 -38
- package/dist/db-DfhipHEF.js.map +1 -0
- package/dist/debug-D7bEv_UV.js +1 -0
- package/dist/{dist-CTzThfu_.js → dist-CuY4ehEk.js} +95 -15
- package/dist/dist-CuY4ehEk.js.map +1 -0
- package/dist/{docker-CRQGQbIt.js → docker-D1HZDS6q.js} +22 -21
- package/dist/docker-D1HZDS6q.js.map +1 -0
- package/dist/docker-DNmMPOBb.js +1 -0
- package/dist/{docker-DV2GNgle.js → docker-DOvjji9I.js} +3 -3
- package/dist/docker-DOvjji9I.js.map +1 -0
- package/dist/{doctor-DlKycx5H.js → doctor-D2MbNO8L.js} +262 -84
- package/dist/doctor-D2MbNO8L.js.map +1 -0
- package/dist/{egress-BhzNh7Yi.js → egress-2-qzPvGi.js} +146 -15
- package/dist/egress-2-qzPvGi.js.map +1 -0
- package/dist/environment-Cca6a4wB.js +1132 -0
- package/dist/environment-Cca6a4wB.js.map +1 -0
- package/dist/{errors-yDAEwmZo.js → errors-B6BSMs03.js} +2 -2
- package/dist/errors-B6BSMs03.js.map +1 -0
- package/dist/{errors-C9w_ud7g.js → errors-r025B0Zb.js} +4 -4
- package/dist/errors-r025B0Zb.js.map +1 -0
- package/dist/evaluate-DKIl54Hb.js +2348 -0
- package/dist/evaluate-DKIl54Hb.js.map +1 -0
- package/dist/evaluate-DMyaGQEj.d.ts +92 -0
- package/dist/files-BAt0i00X.js +1 -0
- package/dist/{files-DJUFg-4e.js → files-BXHZFWmx.js} +5 -5
- package/dist/{files-DJUFg-4e.js.map → files-BXHZFWmx.js.map} +1 -1
- package/dist/files-Bk1mC3AN.js +43 -0
- package/dist/files-Bk1mC3AN.js.map +1 -0
- package/dist/{files-CSzVADHn.js → files-BpUYBwGu.js} +11 -10
- package/dist/{files-CSzVADHn.js.map → files-BpUYBwGu.js.map} +1 -1
- package/dist/files-Bw8lWEjg.js +1 -0
- package/dist/files-D6J0iSdx.js +1 -0
- package/dist/{git-DfG-YJ1u.js → git-Crl3b87o.js} +2 -2
- package/dist/{git-DfG-YJ1u.js.map → git-Crl3b87o.js.map} +1 -1
- package/dist/harness-CEuntrla.js +323 -0
- package/dist/harness-CEuntrla.js.map +1 -0
- package/dist/harness.d.ts +16 -30
- package/dist/harness.js +1 -2
- package/dist/{header-CALhmafp.js → header-DHQv_ZrF.js} +2 -2
- package/dist/{header-CALhmafp.js.map → header-DHQv_ZrF.js.map} +1 -1
- package/dist/{images-BMgdpMAX.js → images-Bi3aGxb9.js} +114 -45
- package/dist/images-Bi3aGxb9.js.map +1 -0
- package/dist/index.d.ts +9 -37
- package/dist/index.js +6 -2138
- package/dist/{init-BIdLF0F9.js → init-Cl3f3NBT.js} +26 -8
- package/dist/init-Cl3f3NBT.js.map +1 -0
- package/dist/init-fhOXED8F.js +1 -0
- package/dist/{invocations-CLaL8AuW.js → invocations-BYDPe3fV.js} +4 -4
- package/dist/{invocations-CLaL8AuW.js.map → invocations-BYDPe3fV.js.map} +1 -1
- package/dist/{judge-CeYPN8P_.js → judge-DW_GDOmB.js} +11 -19
- package/dist/{judge-CeYPN8P_.js.map → judge-DW_GDOmB.js.map} +1 -1
- package/dist/judge.d.ts +2 -2
- package/dist/judge.js +1 -1
- package/dist/{live-S0qODb6o.js → live-jlfszAbb.js} +9 -20
- package/dist/live-jlfszAbb.js.map +1 -0
- package/dist/matchers-Ddmc2z7H.js +118 -0
- package/dist/matchers-Ddmc2z7H.js.map +1 -0
- package/dist/model-CXhrziBe.js +1 -0
- package/dist/{model-B1BZoZ9b.js → model-D0IcOQao.js} +22 -20
- package/dist/model-D0IcOQao.js.map +1 -0
- package/dist/{model-DEQo3FkZ.js → model-DaVCY3rb.js} +2 -2
- package/dist/{model-DEQo3FkZ.js.map → model-DaVCY3rb.js.map} +1 -1
- package/dist/{model-BOnlAPzs.js → model-DaX4bNLZ.js} +123 -22
- package/dist/model-DaX4bNLZ.js.map +1 -0
- package/dist/model-_bxNrk2h.js +1 -0
- package/dist/{node-Co7EXge0.js → node-B3ydHo6t.js} +10 -47
- package/dist/node-B3ydHo6t.js.map +1 -0
- package/dist/object-CLf2-tNq.js +9 -0
- package/dist/object-CLf2-tNq.js.map +1 -0
- package/dist/{opencode-providers-ByD85Q0l.js → opencode-providers-J9colP0-.js} +96 -21
- package/dist/opencode-providers-J9colP0-.js.map +1 -0
- package/dist/options-C0d0AzeR.js +1118 -0
- package/dist/options-C0d0AzeR.js.map +1 -0
- package/dist/{pairs-DDsjgm5F.js → pairs-CnInRu-G.js} +76 -2
- package/dist/{pairs-DDsjgm5F.js.map → pairs-CnInRu-G.js.map} +1 -1
- package/dist/{patch-bDV8YFW0.js → patch-B1bnfhN-.js} +6 -6
- package/dist/{patch-bDV8YFW0.js.map → patch-B1bnfhN-.js.map} +1 -1
- package/dist/{plan-DUydFc_1.js → plan-DgQxp9yy.js} +34 -13
- package/dist/plan-DgQxp9yy.js.map +1 -0
- package/dist/{preflight-BkoSxf4C.js → preflight-BRzD30L5.js} +8 -7
- package/dist/preflight-BRzD30L5.js.map +1 -0
- package/dist/preflight-DlQvbySb.js +1 -0
- package/dist/{prepare-CUAWHebF.js → prepare-B_y-AFMv.js} +38 -30
- package/dist/prepare-B_y-AFMv.js.map +1 -0
- package/dist/prepare-DCKTLHN3.js +2 -0
- package/dist/{provenance-D7HK3vzt.js → provenance-DQ0AXn6c.js} +5 -5
- package/dist/{provenance-D7HK3vzt.js.map → provenance-DQ0AXn6c.js.map} +1 -1
- package/dist/provider-DehR4CMN.js +1 -0
- package/dist/providers-B9yTdIqF.js +1 -0
- package/dist/providers-DoTq1jHW.js +165 -0
- package/dist/providers-DoTq1jHW.js.map +1 -0
- package/dist/query-BO-ux7RB.js +139 -0
- package/dist/query-BO-ux7RB.js.map +1 -0
- package/dist/query-CRQfsw0e.js +1 -0
- package/dist/questions-C2dym2ce.js +524 -0
- package/dist/questions-C2dym2ce.js.map +1 -0
- package/dist/{record-BRP9vZrj.js → record-BKmMvZ6O.js} +9 -1
- package/dist/{record-BRP9vZrj.js.map → record-BKmMvZ6O.js.map} +1 -1
- package/dist/remote-CPPW86Q9.js +378 -0
- package/dist/remote-CPPW86Q9.js.map +1 -0
- package/dist/{results-Di7m8dQ3.js → results-BE2f-1XS.js} +6 -5
- package/dist/results-BE2f-1XS.js.map +1 -0
- package/dist/{resume-SUz1GskV.js → resume-Ca1c8vix.js} +10 -10
- package/dist/{resume-SUz1GskV.js.map → resume-Ca1c8vix.js.map} +1 -1
- package/dist/{root-CvdRlRZX.js → root-DICDHCtp.js} +4 -4
- package/dist/{root-CvdRlRZX.js.map → root-DICDHCtp.js.map} +1 -1
- package/dist/{runtime-DdsfnRz_.js → runtime-PX_oXiCS.js} +23 -71
- package/dist/runtime-PX_oXiCS.js.map +1 -0
- package/dist/sandbox-DLrsvu7U.js +70 -0
- package/dist/sandbox-DLrsvu7U.js.map +1 -0
- package/dist/sandbox.d.ts +19 -6
- package/dist/sandbox.js +1 -2
- package/dist/server-Dp8eOeK5.js +1 -0
- package/dist/{server-BNnXtmS8.js → server-TUY5xA_A.js} +11 -11
- package/dist/server-TUY5xA_A.js.map +1 -0
- package/dist/shared-CA61BuPJ.js +188 -0
- package/dist/shared-CA61BuPJ.js.map +1 -0
- package/dist/{source-Dof0NQBm.js → source-DJGS0a0u.js} +2 -2
- package/dist/{source-Dof0NQBm.js.map → source-DJGS0a0u.js.map} +1 -1
- package/dist/source-DdbLGR1M.js +1 -0
- package/dist/src-BxfCKA3_.js +2 -0
- package/dist/{src-C1E9HDWO.js → src-C7_1SqDP.js} +3 -3
- package/dist/{src-C1E9HDWO.js.map → src-C7_1SqDP.js.map} +1 -1
- package/dist/{src-DDNXsD9H.js → src-CF17Ug08.js} +5 -4
- package/dist/src-CF17Ug08.js.map +1 -0
- package/dist/{src-C_4Umgh2.js → src-CFp86GtV.js} +52 -51
- package/dist/src-CFp86GtV.js.map +1 -0
- package/dist/src-CLoOMVWI.js +3 -0
- package/dist/src-CcrQbpG8.js +2 -0
- package/dist/src-DCFtJRp0.js +2 -0
- package/dist/src-DIL0jST0.js +368 -0
- package/dist/src-DIL0jST0.js.map +1 -0
- package/dist/src-DSYYPEOj.js +2 -0
- package/dist/src-DYO3DdnQ.js +1 -0
- package/dist/src-DvCg0NBs.js +324 -0
- package/dist/src-DvCg0NBs.js.map +1 -0
- package/dist/{src-COgE7991.js → src-DvK0EIZx.js} +5 -5
- package/dist/{src-COgE7991.js.map → src-DvK0EIZx.js.map} +1 -1
- package/dist/{src-DVy58-zZ.js → src-FGXhkEkH.js} +5 -5
- package/dist/{src-DVy58-zZ.js.map → src-FGXhkEkH.js.map} +1 -1
- package/dist/src-WD-zgQYZ.js +2 -0
- package/dist/src-_oI9AePb.js +2 -0
- package/dist/{src-8rRXy4u9.js → src-ftu5wPSt.js} +1652 -182
- package/dist/src-ftu5wPSt.js.map +1 -0
- package/dist/src-qsKCqv7a.js +2 -0
- package/dist/{src-D95O-Smv.js → src-u8_ixn_F.js} +15 -11
- package/dist/src-u8_ixn_F.js.map +1 -0
- package/dist/state-CeR6oz0k.js +66 -0
- package/dist/state-CeR6oz0k.js.map +1 -0
- package/dist/{state-CiXyLqg0.js → state-DfpHox6b.js} +36 -24
- package/dist/state-DfpHox6b.js.map +1 -0
- package/dist/state-DpAxYyED.js +1 -0
- package/dist/studio-app/assets/index-CBkhNAv8.js +58 -0
- package/dist/studio-app/assets/index-CwQN3f_h.css +2 -0
- package/dist/studio-app/index.html +2 -2
- package/dist/suite-Cw_7G2RM.js +429 -0
- package/dist/suite-Cw_7G2RM.js.map +1 -0
- package/dist/suite-DamAEnNF.js +1 -0
- package/dist/summary-BUKfU-9l.js +611 -0
- package/dist/summary-BUKfU-9l.js.map +1 -0
- package/dist/{summary-2OAWF2N7.js → summary-Baa4rTHS.js} +7 -7
- package/dist/{summary-2OAWF2N7.js.map → summary-Baa4rTHS.js.map} +1 -1
- package/dist/testing.d.ts +3 -2
- package/dist/testing.js +3 -2
- package/dist/{tool-target-BU1JnSE9.js → tool-target-D2mcYlSQ.js} +14 -7
- package/dist/tool-target-D2mcYlSQ.js.map +1 -0
- package/dist/{tools-B10Uj7OI.js → tools-DLHgetpx.js} +6 -6
- package/dist/tools-DLHgetpx.js.map +1 -0
- package/dist/transcript-BNNANU8A.js +64 -0
- package/dist/transcript-BNNANU8A.js.map +1 -0
- package/dist/{types-D8RLfkFP.d.ts → types-BYdlwMwo.d.ts} +398 -46
- package/dist/{usage-DDF_6tkV.js → usage-CleNKkly.js} +4 -2
- package/dist/{usage-DDF_6tkV.js.map → usage-CleNKkly.js.map} +1 -1
- package/dist/view/index.d.ts +290 -8
- package/dist/view/index.js +7 -7
- package/dist/{views-Dqa5ojfZ.js → views-B8w2J-gD.js} +19 -24
- package/dist/views-B8w2J-gD.js.map +1 -0
- package/dist/views-DblnI3kA.js +2 -0
- package/dist/{worker-entry-BAikHl9F.js → worker-entry-DNnveLqx.js} +16 -229
- package/dist/worker-entry-DNnveLqx.js.map +1 -0
- package/dist/{workspace-C-TrNIUc.js → workspace-C4RA3srV.js} +7 -7
- package/dist/{workspace-C-TrNIUc.js.map → workspace-C4RA3srV.js.map} +1 -1
- package/dist/workspace-C_X0tdv6.js +1 -0
- package/package.json +12 -12
- package/dist/agent-WSmTaesf.js.map +0 -1
- package/dist/assemble-BCZzTyR7.js.map +0 -1
- package/dist/assemble-C5qq5yT2.js +0 -2
- package/dist/capture-Bp74Z6Fr.js.map +0 -1
- package/dist/capture-DS-cPyuE.js +0 -2
- package/dist/cli-CVrMh_qz.js.map +0 -1
- package/dist/cli-spec-CKEYa8V2.js.map +0 -1
- package/dist/client-Bbo9SPlK.js.map +0 -1
- package/dist/command-BrmXUC-W.js +0 -2
- package/dist/command-C5xFPKHM.js.map +0 -1
- package/dist/command-CzIFp1JQ.js.map +0 -1
- package/dist/command-DUT5l13I.js.map +0 -1
- package/dist/commands-CLmPBrTS.js.map +0 -1
- package/dist/commands-fI3G2Wnh.js +0 -2
- package/dist/config-CGvBeewH.js +0 -457
- package/dist/config-CGvBeewH.js.map +0 -1
- package/dist/context-DbSMEVKs.js.map +0 -1
- package/dist/db-CSSFtGcN.js +0 -3
- package/dist/db-CfQFwJtq.js.map +0 -1
- package/dist/debug-SEmW4w8f.js +0 -2
- package/dist/dist-CTzThfu_.js.map +0 -1
- package/dist/docker-CRQGQbIt.js.map +0 -1
- package/dist/docker-DV2GNgle.js.map +0 -1
- package/dist/docker-Df_YOxxB.js +0 -2
- package/dist/doctor-DlKycx5H.js.map +0 -1
- package/dist/duration-JsJrIWXq.js +0 -26
- package/dist/duration-JsJrIWXq.js.map +0 -1
- package/dist/egress-BhzNh7Yi.js.map +0 -1
- package/dist/env-BQqj93gu.js +0 -21
- package/dist/env-BQqj93gu.js.map +0 -1
- package/dist/environment-1jE-HhO-.js +0 -657
- package/dist/environment-1jE-HhO-.js.map +0 -1
- package/dist/errors-C9w_ud7g.js.map +0 -1
- package/dist/errors-yDAEwmZo.js.map +0 -1
- package/dist/fake-instructions-CUI33I_F.js +0 -32
- package/dist/fake-instructions-CUI33I_F.js.map +0 -1
- package/dist/family-D2b7WD10.js +0 -67
- package/dist/family-D2b7WD10.js.map +0 -1
- package/dist/files-AiEKuEZe.js +0 -2
- package/dist/files-BM78_nnF.js +0 -2
- package/dist/files-BMCvfB4X.js +0 -2
- package/dist/harness-CeCp2GSW.js +0 -171
- package/dist/harness-CeCp2GSW.js.map +0 -1
- package/dist/images-BMgdpMAX.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/init-B6vegaP_.js +0 -2
- package/dist/init-BIdLF0F9.js.map +0 -1
- package/dist/jiti-BcJHCtrb.js +0 -46
- package/dist/jiti-BcJHCtrb.js.map +0 -1
- package/dist/live-S0qODb6o.js.map +0 -1
- package/dist/model-B1BZoZ9b.js.map +0 -1
- package/dist/model-BOnlAPzs.js.map +0 -1
- package/dist/model-DWeg5J7z.js +0 -2
- package/dist/model-FXko2kl7.js +0 -2
- package/dist/node-Co7EXge0.js.map +0 -1
- package/dist/opencode-providers-ByD85Q0l.js.map +0 -1
- package/dist/options-CKqyPGxX.js +0 -739
- package/dist/options-CKqyPGxX.js.map +0 -1
- package/dist/plan-DUydFc_1.js.map +0 -1
- package/dist/preflight-BkoSxf4C.js.map +0 -1
- package/dist/preflight-D5LW7Oqi.js +0 -2
- package/dist/prepare-CUAWHebF.js.map +0 -1
- package/dist/prepare-oji2oO8W.js +0 -3
- package/dist/process-CSVO_jsc.js +0 -119
- package/dist/process-CSVO_jsc.js.map +0 -1
- package/dist/provider-BDbNOQhQ.js +0 -2
- package/dist/providers-B1DzMvmH.js +0 -107
- package/dist/providers-B1DzMvmH.js.map +0 -1
- package/dist/providers-CpEfNU1F.js +0 -2
- package/dist/query-BjBMLa-C.js +0 -91
- package/dist/query-BjBMLa-C.js.map +0 -1
- package/dist/query-oPO_XrTy.js +0 -2
- package/dist/questions-B8by4Bbt.js +0 -234
- package/dist/questions-B8by4Bbt.js.map +0 -1
- package/dist/results-Di7m8dQ3.js.map +0 -1
- package/dist/runtime-DdsfnRz_.js.map +0 -1
- package/dist/sandbox-D5v-of6d.js +0 -139
- package/dist/sandbox-D5v-of6d.js.map +0 -1
- package/dist/server-BNnXtmS8.js.map +0 -1
- package/dist/server-BNwoKzlm.js +0 -2
- package/dist/source-Bk_0ZaEY.js +0 -2
- package/dist/src-8rRXy4u9.js.map +0 -1
- package/dist/src-BGWuWp6I.js +0 -3
- package/dist/src-BRt0O02i.js +0 -3
- package/dist/src-Bx2x1ekD.js +0 -3
- package/dist/src-C_4Umgh2.js.map +0 -1
- package/dist/src-D95O-Smv.js.map +0 -1
- package/dist/src-DDNXsD9H.js.map +0 -1
- package/dist/src-DLa-ESW0.js +0 -4
- package/dist/src-cN0aB-WR.js +0 -3
- package/dist/src-cdIVjC5b.js +0 -2
- package/dist/src-sIsDHWYq.js +0 -3
- package/dist/state-Bo5zg4da.js +0 -2
- package/dist/state-CiXyLqg0.js.map +0 -1
- package/dist/studio-app/assets/index-B9MCTfuu.css +0 -2
- package/dist/studio-app/assets/index-CROH2NB3.js +0 -58
- package/dist/summary-BTksrIxn.js +0 -254
- package/dist/summary-BTksrIxn.js.map +0 -1
- package/dist/tool-target-BU1JnSE9.js.map +0 -1
- package/dist/tools-B10Uj7OI.js.map +0 -1
- package/dist/views-Cs2Oqayd.js +0 -3
- package/dist/views-Dqa5ojfZ.js.map +0 -1
- package/dist/worker-entry-BAikHl9F.js.map +0 -1
- package/dist/workspace-Do_SiWBs.js +0 -2
|
@@ -0,0 +1,1118 @@
|
|
|
1
|
+
import { t as isObjectLike } from "./object-CLf2-tNq.js";
|
|
2
|
+
import { n as UsageError } from "./errors-B6BSMs03.js";
|
|
3
|
+
import { c as jsonValueSchema, d as refusal, l as nonEmpty, p as parseDuration, t as closest, u as oneOf } from "./shared-CA61BuPJ.js";
|
|
4
|
+
import { f as FACTORY, m as harnessProblem, p as harnessDescriptorSchema, u as opencodeModelError } from "./harness-CEuntrla.js";
|
|
5
|
+
import { A as workspaceLabel, L as gateProblem, T as sandboxOf, _ as isEnvironment, i as applied, j as workspaceOf, k as unbrokeredSecrets, l as emptyEnvironment, o as checkEgress, r as EMPTY_BASELINE, s as checkEnv, u as environmentDataProblem, w as plannedSecrets, y as isMovingRef, z as sandboxProblem } from "./environment-Cca6a4wB.js";
|
|
6
|
+
import { l as sha256 } from "./ids-Bp6ut7ZD.js";
|
|
7
|
+
import { i as checkModelString } from "./judge-DW_GDOmB.js";
|
|
8
|
+
import { g as isAgentKind, i as plannedEgress, l as HARNESS_EXAMPLE, r as egressLabel } from "./egress-2-qzPvGi.js";
|
|
9
|
+
import { a as showsNothing, n as mergeQuestion, o as withQuestionDefaults } from "./state-CeR6oz0k.js";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
//#region src/contract/compare.ts
|
|
13
|
+
const pairFactorSchema = z.enum(["environment", "harness"]).describe("A matrix factor a compare question varies when pairing; the others are held.");
|
|
14
|
+
const compareGateSchema = z.enum([
|
|
15
|
+
"variants",
|
|
16
|
+
"all",
|
|
17
|
+
"none"
|
|
18
|
+
]);
|
|
19
|
+
/** Evaluation models score yes/no questions only, so they can't pick between two attempts. */
|
|
20
|
+
const isEvaluationModel = (model) => model.startsWith("typesafe-ai/");
|
|
21
|
+
const questionStateSchema = z.strictObject({
|
|
22
|
+
task: z.boolean().optional().describe("The prompts the attempt was sent, once for both sides when they match. Default true."),
|
|
23
|
+
output: z.boolean().optional().describe("The final message: the agent's own account of its work. Default false."),
|
|
24
|
+
files: z.boolean().optional().describe("`status path` lines for the changed files, under `diff`'s patterns. Default false."),
|
|
25
|
+
diff: z.union([z.boolean(), z.array(nonEmpty()).min(1)]).optional().describe("The diff since the eval started. A list is gitignore-style patterns over workspace paths: the last matching pattern wins and `!` excludes. Default true."),
|
|
26
|
+
transcript: z.union([z.boolean(), z.enum(["tools", "full"])]).optional().describe("The transcript as text lines: `true` is messages and tool calls with tool output clipped, `'tools'` tool calls only, `'full'` adds reasoning and whole tool output. It names each harness's tools, so a pair of two harnesses isn't blind. Default false."),
|
|
27
|
+
turns: z.enum(["last", "all"]).optional().describe("`'all'`: every turn under `turns`, each with its task, output and (with `transcript`) transcript; `'last'`: the last turn only. Default `'all'`."),
|
|
28
|
+
checks: z.union([z.boolean(), z.array(nonEmpty())]).optional().describe("`c.exec` results; a list shows only those commands, as passed to `c.exec`. Default false.")
|
|
29
|
+
}).describe("What each side of a compare question shows: the keys of `judge.state` but `contents` (the attempts' workspaces are gone when questions are asked). An unset key keeps its default, which is what a question shows with no `state` at all: the task and the diff.");
|
|
30
|
+
const questionArtifactsSchema = z.union([z.literal(true), z.array(nonEmpty()).min(1)]);
|
|
31
|
+
const questionModelSchema = z.string().refine((m) => m.includes("/"), { params: { provider: true } }).refine((m) => !isEvaluationModel(m), { params: { evaluation: true } });
|
|
32
|
+
const questionOptionsSchema = z.strictObject({
|
|
33
|
+
model: questionModelSchema.optional().describe("`'provider/model'` that answers every question below it without its own `model`."),
|
|
34
|
+
state: questionStateSchema.optional().describe("What each side shows."),
|
|
35
|
+
artifacts: questionArtifactsSchema.optional().describe("Each side's own captures, by name; `true` sends all of them. Default none.")
|
|
36
|
+
}).describe("Defaults for every compare question: `model` and `artifacts` replaced, `state` merged key by key.");
|
|
37
|
+
const questionObjectSchema = z.strictObject({
|
|
38
|
+
question: nonEmpty(),
|
|
39
|
+
model: questionModelSchema.optional().describe("`'provider/model'` that answers it, over the cascaded `question.model`."),
|
|
40
|
+
across: pairFactorSchema.optional().describe("The factor it varies when pairing."),
|
|
41
|
+
state: questionStateSchema.optional().describe("What each side shows, over the cascaded `question.state`, key by key."),
|
|
42
|
+
artifacts: questionArtifactsSchema.optional().describe("Each side's own captures, by name; `true` sends all of them. Replaces `question.artifacts`.")
|
|
43
|
+
});
|
|
44
|
+
const compareQuestionSchema = z.union([nonEmpty(), questionObjectSchema]).describe("A question for pairwise judgement: its text, or the text and the `'provider/model'` that answers it. Without a model, the cascaded `question.model` answers, else `analysis.model`, else the judge's when it's a language model. `across` is the factor it varies when pairing; without it (and without a one-factor `compare.across`) both pair kinds run. `state` and `artifacts` choose what each side shows.");
|
|
45
|
+
/** The first label two items share. */
|
|
46
|
+
const twice = (labels) => labels.find((k, i) => labels.indexOf(k) !== i);
|
|
47
|
+
function unique(label) {
|
|
48
|
+
return (list, ctx) => {
|
|
49
|
+
if (twice(list.map(label)) !== void 0) ctx.addIssue({
|
|
50
|
+
code: "custom",
|
|
51
|
+
message: "twice"
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const questionText = (q) => typeof q === "string" ? q : q.question;
|
|
56
|
+
/** The compare object, with environments checked by `environment`. */
|
|
57
|
+
const compareSchema = (environment) => z.strictObject({
|
|
58
|
+
baseline: environment.optional(),
|
|
59
|
+
variants: z.array(environment).min(1),
|
|
60
|
+
gate: compareGateSchema.optional(),
|
|
61
|
+
minLift: z.number().min(-1).max(1).optional(),
|
|
62
|
+
harness: z.array(z.union([z.string(), harnessDescriptorSchema])).optional(),
|
|
63
|
+
questions: z.array(compareQuestionSchema).min(1).superRefine(unique(questionText)).optional(),
|
|
64
|
+
across: z.union([pairFactorSchema, z.array(pairFactorSchema).min(1).superRefine(unique(String))]).optional()
|
|
65
|
+
});
|
|
66
|
+
compareSchema(z.custom());
|
|
67
|
+
const QUESTION_EXAMPLE = `questions: ['Which handles state more cleanly?']`;
|
|
68
|
+
const ACROSS_HINT = `'environment' (vary the environment, same harness) or 'harness' (vary the harness, same environment)`;
|
|
69
|
+
const ACROSS_MEAN = " Did you mean \"across\"? It is the factor you vary: across: 'environment'.";
|
|
70
|
+
const QUESTION_KEYS = Object.keys(questionObjectSchema.shape);
|
|
71
|
+
const QUESTION_SHAPE = "{ question, model?, across?, state?, artifacts? }";
|
|
72
|
+
const STATE_KEYS$1 = Object.keys(questionStateSchema.shape);
|
|
73
|
+
const STATE_VALUES = {
|
|
74
|
+
task: "true or false",
|
|
75
|
+
output: "true or false",
|
|
76
|
+
files: "true or false",
|
|
77
|
+
diff: "true, false or a non-empty list of patterns",
|
|
78
|
+
transcript: "true, false, 'tools' or 'full'",
|
|
79
|
+
turns: "'last' or 'all'",
|
|
80
|
+
checks: "true, false or a list of commands"
|
|
81
|
+
};
|
|
82
|
+
const describe$1 = (value) => {
|
|
83
|
+
if (Array.isArray(value)) return `a list of ${value.length}`;
|
|
84
|
+
if (typeof value === "object" && value !== null) {
|
|
85
|
+
const keys = Object.keys(value);
|
|
86
|
+
return keys.length ? `an object with ${keys.map((k) => `"${k}"`).join(", ")}` : "an object";
|
|
87
|
+
}
|
|
88
|
+
return JSON.stringify(value) ?? String(value);
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* What's wrong with a question's `state` or `artifacts`, as one sentence, or undefined. `name`
|
|
92
|
+
* turns a key into how the sentence names it: `question.state` on the option,
|
|
93
|
+
* `compare.questions "…" state` on an entry.
|
|
94
|
+
*/
|
|
95
|
+
function questionPartsProblem(value, name, where) {
|
|
96
|
+
const { state, artifacts } = value;
|
|
97
|
+
if (state !== void 0) {
|
|
98
|
+
if (!isObjectLike(state) || Array.isArray(state)) return `${name("state")} in ${where} must be an object such as { transcript: 'tools' } (got ${describe$1(state)})`;
|
|
99
|
+
for (const [key, v] of Object.entries(state)) {
|
|
100
|
+
if (key === "contents") return `${name("state.contents")} in ${where} can't be set: questions are asked after the attempts, when their workspaces are gone. Narrow state.diff instead.`;
|
|
101
|
+
const schema = questionStateSchema.shape[key];
|
|
102
|
+
if (!schema) {
|
|
103
|
+
const hint = closest(key, STATE_KEYS$1);
|
|
104
|
+
return `Unknown ${name("state")} key "${key}" in ${where}.${hint ? ` Did you mean "${hint}"?` : ""}`;
|
|
105
|
+
}
|
|
106
|
+
if (!schema.safeParse(v).success) return `${name(`state.${key}`)} in ${where} must be ${STATE_VALUES[key]} (got ${describe$1(v)})`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (artifacts !== void 0 && !questionArtifactsSchema.safeParse(artifacts).success) return `${name("artifacts")} in ${where} must be true or a non-empty list of capture names (got ${describe$1(artifacts)})`;
|
|
110
|
+
}
|
|
111
|
+
/** What's wrong with the `question` option set in `where`, as one sentence, or undefined. */
|
|
112
|
+
function questionOptionsProblem(value, where) {
|
|
113
|
+
if (!isObjectLike(value) || Array.isArray(value)) return `question in ${where} is { model?, state?, artifacts? } (got ${describe$1(value)}), e.g. question: { model: 'google/gemini-3.8-flash' }`;
|
|
114
|
+
const keys = Object.keys(questionOptionsSchema.shape);
|
|
115
|
+
for (const key of Object.keys(value)) {
|
|
116
|
+
if (keys.includes(key)) continue;
|
|
117
|
+
if (key === "across") return `Unknown question option "across" in ${where}. across goes on each compare.questions entry, { question, across }, or on compare.across.`;
|
|
118
|
+
const hint = closest(key, keys);
|
|
119
|
+
return `Unknown question option "${key}" in ${where}.${hint ? ` Did you mean "${hint}"?` : ""} question takes model, state and artifacts.`;
|
|
120
|
+
}
|
|
121
|
+
const { model } = value;
|
|
122
|
+
if (model !== void 0 && typeof model === "string" && isEvaluationModel(model)) return `question.model in ${where} names ${model}, an evaluation model: it scores yes/no questions and can't pick between two attempts. Use a language model, e.g. 'google/gemini-3.8-flash'.`;
|
|
123
|
+
if (model !== void 0 && !questionModelSchema.safeParse(model).success) return `question.model in ${where} is a 'provider/model' string, e.g. 'google/gemini-3.8-flash' (got ${describe$1(model)})`;
|
|
124
|
+
return questionPartsProblem(value, (key) => `question.${key}`, where);
|
|
125
|
+
}
|
|
126
|
+
/** One question's sentence: the union refuses it whole, so the branch it meant says why. */
|
|
127
|
+
function questionProblem(item, where) {
|
|
128
|
+
if (typeof item === "string") return `compare.questions in ${where} is an empty string. Ask something, e.g. ${QUESTION_EXAMPLE}.`;
|
|
129
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) return `compare.questions in ${where} takes strings or ${QUESTION_SHAPE} (got ${describe$1(item)}), e.g. ${QUESTION_EXAMPLE}.`;
|
|
130
|
+
const unknown = Object.keys(item).filter((k) => !QUESTION_KEYS.includes(k));
|
|
131
|
+
if (unknown.length) {
|
|
132
|
+
const hint = unknown.some((k) => k === "cut" || k === "cuts") ? ACROSS_MEAN : "";
|
|
133
|
+
return `compare.questions in ${where} takes ${QUESTION_SHAPE} (got ${unknown.map((k) => `"${k}"`).join(", ")}).${hint}`;
|
|
134
|
+
}
|
|
135
|
+
const { question, model, across } = item;
|
|
136
|
+
const issues = questionObjectSchema.safeParse(item).error?.issues ?? [];
|
|
137
|
+
const at = (key) => issues.find((i) => i.path[0] === key);
|
|
138
|
+
if (at("question")) return `compare.questions in ${where} has an entry without its question${question === void 0 ? "" : ` (got ${describe$1(question)})`}, e.g. { question: 'Which is cleaner?', model: 'google/gemini-3.8-flash' }.`;
|
|
139
|
+
if (at("across")) return `compare.questions "${question}" in ${where}: across is ${ACROSS_HINT} (got ${describe$1(across)}). Leave it out to vary both.`;
|
|
140
|
+
const parts = questionPartsProblem(item, (key) => `compare.questions "${question}" ${key}`, where);
|
|
141
|
+
if (parts) return parts;
|
|
142
|
+
if (at("model")?.params?.evaluation) return `compare.questions "${question}" in ${where} names ${model}, an evaluation model: it scores yes/no questions and can't pick between two attempts. Use a language model, e.g. 'google/gemini-3.8-flash'.`;
|
|
143
|
+
return `compare.questions "${question}" in ${where}: model is a 'provider/model' string, e.g. 'google/gemini-3.8-flash' (got ${describe$1(model)})`;
|
|
144
|
+
}
|
|
145
|
+
function explain(where) {
|
|
146
|
+
return (issue, value) => {
|
|
147
|
+
const spec = value;
|
|
148
|
+
if (issue.code === "unrecognized_keys" && issue.path.length === 0) {
|
|
149
|
+
const key = issue.keys[0];
|
|
150
|
+
return `Unknown compare option "${key}" in ${where}${key === "question" ? `. Did you mean "questions"? It's a list: ${QUESTION_EXAMPLE}.` : key === "cut" || key === "cuts" ? `.${ACROSS_MEAN}` : `. compare takes ${Object.keys(compareSchema(z.any()).shape).join(", ")}.`}`;
|
|
151
|
+
}
|
|
152
|
+
const [key, index] = issue.path;
|
|
153
|
+
const v = spec[key];
|
|
154
|
+
switch (key) {
|
|
155
|
+
case "questions":
|
|
156
|
+
if (issue.code === "custom" && index === void 0) return `compare.questions in ${where} asks "${twice(v.map(questionText))}" twice. Ask each question once.`;
|
|
157
|
+
if (index !== void 0) return questionProblem(v[index], where);
|
|
158
|
+
return Array.isArray(v) ? `compare.questions in ${where} is an empty list. Ask at least one, e.g. ${QUESTION_EXAMPLE}, or leave questions out.` : `compare.questions in ${where} is a list (got ${describe$1(v)}), e.g. ${QUESTION_EXAMPLE}.`;
|
|
159
|
+
case "across": {
|
|
160
|
+
const list = Array.isArray(v) ? v : [v];
|
|
161
|
+
if (list.length === 0) return `compare.across in ${where} is ${ACROSS_HINT}, or a list of those (got ${describe$1(v)}). Leave it out to vary both.`;
|
|
162
|
+
const bad = list.findIndex((f) => !pairFactorSchema.safeParse(f).success);
|
|
163
|
+
return bad === -1 ? `compare.across in ${where} lists "${twice(list)}" twice. List each factor once.` : `compare.across in ${where} is ${ACROSS_HINT}, or a list of those (got ${describe$1(list[bad])}).`;
|
|
164
|
+
}
|
|
165
|
+
case "baseline": return `compare.baseline in ${where} must be an environment from defineEnvironment (got ${describe$1(v)}). It is the control; variants are not layered on it or on the eval's environment. Leave it out to use the eval's environment as the control.`;
|
|
166
|
+
case "gate": return `compare.gate in ${where} is ${oneOf(compareGateSchema.options)} (got ${describe$1(v)})`;
|
|
167
|
+
case "minLift": return `compare.minLift in ${where} is points from -1 to 1, e.g. minLift: 0.1 (got ${describe$1(v)})`;
|
|
168
|
+
case "harness": {
|
|
169
|
+
const item = index === void 0 ? void 0 : v[index];
|
|
170
|
+
const kind = item?.kind;
|
|
171
|
+
const problem = kind && kind in FACTORY ? harnessProblem(item) : void 0;
|
|
172
|
+
return problem ? `compare.harness in ${where}: ${problem}` : `compare.harness in ${where} must be a list of harness ids, e.g. harness: ['cursor:composer-2.5'] (got ${describe$1(index === void 0 ? v : item)})`;
|
|
173
|
+
}
|
|
174
|
+
default: return index === void 0 ? `compare has no variants in ${where}. Give at least one environment.` : `compare.variants in ${where} must be environments from defineEnvironment (got ${describe$1(v[index])}). Variants share the eval's prompt and checks; if a variant needs different checks, write a separate eval.`;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/** The order the hand-written checks ran in, so the first refusal stays the same. */
|
|
179
|
+
const ORDER = [
|
|
180
|
+
"questions",
|
|
181
|
+
"across",
|
|
182
|
+
"baseline",
|
|
183
|
+
"gate",
|
|
184
|
+
"minLift",
|
|
185
|
+
"harness",
|
|
186
|
+
"variants"
|
|
187
|
+
];
|
|
188
|
+
/**
|
|
189
|
+
* What's wrong with a compare object whose environments `environment` checks, or undefined.
|
|
190
|
+
* `where` names the group or eval it's set on.
|
|
191
|
+
*/
|
|
192
|
+
function compareProblem(value, where, environment) {
|
|
193
|
+
if (Array.isArray(value)) return `compare in ${where} must be an object, not a list. Use compare: { baseline, variants: [...] }.`;
|
|
194
|
+
if (!isObjectLike(value) || !Array.isArray(value.variants)) return `compare in ${where} takes { variants, baseline? } (got ${describe$1(value)}). Use compare: { variants: [a, b], baseline?, gate?, minLift?, harness?, questions?, across? }.`;
|
|
195
|
+
return refusal(compareSchema(environment), value, explain(where), { order: ORDER });
|
|
196
|
+
}
|
|
197
|
+
//#endregion
|
|
198
|
+
//#region src/judge/artifacts.ts
|
|
199
|
+
/** A capture handle: a name plus the file the attempt wrote. */
|
|
200
|
+
function isCaptureHandle(value) {
|
|
201
|
+
return isObjectLike(value) && typeof value.name === "string" && isObjectLike(value.file) && typeof value.file.path === "string";
|
|
202
|
+
}
|
|
203
|
+
/** The first `artifacts` value that isn't `true` or a list of capture handles or names. */
|
|
204
|
+
function badJudgeArtifacts(artifacts) {
|
|
205
|
+
if (artifacts === void 0 || artifacts === true) return void 0;
|
|
206
|
+
if (!Array.isArray(artifacts)) return `must be true or a list of capture handles or names (got ${JSON.stringify(artifacts)})`;
|
|
207
|
+
for (const item of artifacts) {
|
|
208
|
+
if (typeof item === "string" && item.trim()) continue;
|
|
209
|
+
if (isCaptureHandle(item)) continue;
|
|
210
|
+
return `must be true or a list of capture handles or names (got ${JSON.stringify(item)})`;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const nameOf = (item) => typeof item === "string" ? item.trim() : item.name;
|
|
214
|
+
/**
|
|
215
|
+
* Resolves call `artifacts` (capture handles or their names, or `true` for every capture so far,
|
|
216
|
+
* in capture order) to PNG bytes this attempt captured. Unknown names throw: a mistake in the
|
|
217
|
+
* call, before the provider is asked.
|
|
218
|
+
*/
|
|
219
|
+
function resolveJudgeArtifacts(artifacts, blobs) {
|
|
220
|
+
const items = artifacts === true ? [...blobs.keys()] : artifacts;
|
|
221
|
+
if (!items?.length) return [];
|
|
222
|
+
return items.map((item) => {
|
|
223
|
+
const name = nameOf(item);
|
|
224
|
+
const data = blobs.get(name);
|
|
225
|
+
if (!data) throw new Error(`c.judge() artifacts names a capture this attempt doesn't have: ${JSON.stringify(name)}`);
|
|
226
|
+
return {
|
|
227
|
+
name,
|
|
228
|
+
path: `artifacts/${name}.png`,
|
|
229
|
+
sha256: sha256(data),
|
|
230
|
+
bytes: data.byteLength,
|
|
231
|
+
mediaType: "image/png",
|
|
232
|
+
data
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
const filePartsOf = (resolved) => resolved.map((a) => ({
|
|
237
|
+
type: "file",
|
|
238
|
+
mediaType: a.mediaType,
|
|
239
|
+
data: a.data
|
|
240
|
+
}));
|
|
241
|
+
const artifactRefsOf = (resolved) => resolved.map(({ data: _data, ...ref }) => ref);
|
|
242
|
+
/**
|
|
243
|
+
* JSON-only state is a plain object, as TypeSafe and other JSON judges read it.
|
|
244
|
+
* File parts are an ordered decide-state array: one JSON part, then each capture.
|
|
245
|
+
*/
|
|
246
|
+
function decideState(json, files) {
|
|
247
|
+
if (files.length === 0) return json;
|
|
248
|
+
return [{
|
|
249
|
+
type: "json",
|
|
250
|
+
value: json
|
|
251
|
+
}, ...files];
|
|
252
|
+
}
|
|
253
|
+
//#endregion
|
|
254
|
+
//#region src/contract/judge.ts
|
|
255
|
+
const judgeStateSchema = z.strictObject({
|
|
256
|
+
task: z.boolean().optional().describe("The graded turn's prompt. Default true."),
|
|
257
|
+
output: z.boolean().optional().describe("The graded turn's final message, or `on`'s value. Default true."),
|
|
258
|
+
files: z.boolean().optional().describe("`status path` lines for the changed files, under `diff`'s patterns. Default true."),
|
|
259
|
+
diff: z.union([z.boolean(), z.array(nonEmpty()).min(1)]).optional().describe("The diff since the eval started. A list is gitignore-style patterns over workspace paths: the last matching pattern wins and `!` excludes. Default true."),
|
|
260
|
+
contents: z.array(nonEmpty()).optional().describe("Workspace files shown whole, as they are now, changed or not: exact paths or gitignore-style patterns (`'**'`, `'src/**'`; the last match wins, `!` excludes) over the files git sees in the workspace. A missing exact path is `null`; a pattern that matches nothing adds nothing. These files leave the diff. Default none."),
|
|
261
|
+
transcript: z.union([z.boolean(), z.enum(["tools", "full"])]).optional().describe("The graded turn's transcript as text lines: `true` is messages and tool calls with tool output clipped, `'tools'` tool calls only, `'full'` adds reasoning and whole tool output. Default false."),
|
|
262
|
+
turns: z.enum(["last", "all"]).optional().describe("`'all'`: every turn up to the graded one under `turns`, each with its task, output and (with `transcript`) transcript. Default `'last'`."),
|
|
263
|
+
checks: z.union([z.boolean(), z.array(nonEmpty())]).optional().describe("`c.exec` results so far; a list shows only those commands, as passed to `c.exec`. Default false.")
|
|
264
|
+
}).describe("What the judge is given: each key is a key of the state in `judge-input.json`. An unset key keeps its default, which is what the judge gets with no `state` option at all.");
|
|
265
|
+
const judgeFields = {
|
|
266
|
+
threshold: z.number().min(0, "must be between 0 and 1").max(1, "must be between 0 and 1").optional().describe("Minimum score (0–1) for a `pass` verdict."),
|
|
267
|
+
providerOptions: z.record(z.string(), z.record(z.string(), jsonValueSchema)).optional().describe("AI SDK provider options for the judge's calls, keyed by provider."),
|
|
268
|
+
maxConcurrent: z.number().int().min(1).optional().describe("The most judge calls to this model's provider at once, across the run."),
|
|
269
|
+
state: judgeStateSchema.optional().describe("The judge's state: what the judge is given beside the question, not the workspace's or the attempt's state. Merged key by key down the cascade.")
|
|
270
|
+
};
|
|
271
|
+
const fakeJudgeDataSchema = z.strictObject({
|
|
272
|
+
kind: z.literal("fake-judge"),
|
|
273
|
+
score: z.number().min(0).max(1).describe("The score (0–1) every call gets.")
|
|
274
|
+
}).describe("`fakeJudge()` as data: the score every call gets.");
|
|
275
|
+
z.string().regex(/\//, "must be a 'provider/model' string, e.g. 'google/gemini-3.8-flash'");
|
|
276
|
+
const configJudgeDataSchema = z.strictObject({
|
|
277
|
+
model: z.union([z.string(), fakeJudgeDataSchema]).optional().describe("`'provider/model'`, or a fake judge."),
|
|
278
|
+
...judgeFields
|
|
279
|
+
}).describe("`ConfigJudgeOptions` as data: the model by name, or a fake judge with a fixed score.");
|
|
280
|
+
const judgeOptionsSchema = z.strictObject({
|
|
281
|
+
model: z.custom().optional(),
|
|
282
|
+
...judgeFields
|
|
283
|
+
});
|
|
284
|
+
const judgeCallSchema = judgeOptionsSchema.extend({
|
|
285
|
+
on: z.custom().optional(),
|
|
286
|
+
artifacts: z.union([z.literal(true), z.array(z.custom())]).optional()
|
|
287
|
+
});
|
|
288
|
+
const STATE_KEYS = Object.keys(judgeStateSchema.shape);
|
|
289
|
+
const JUDGE_HINTS = {
|
|
290
|
+
see: "state",
|
|
291
|
+
shows: "state",
|
|
292
|
+
show: "state",
|
|
293
|
+
input: "state"
|
|
294
|
+
};
|
|
295
|
+
const VALUES = {
|
|
296
|
+
task: "true or false",
|
|
297
|
+
output: "true or false",
|
|
298
|
+
files: "true or false",
|
|
299
|
+
diff: "true, false or a non-empty list of patterns",
|
|
300
|
+
contents: "a list of paths or patterns",
|
|
301
|
+
transcript: "true, false, 'tools' or 'full'",
|
|
302
|
+
turns: "'last' or 'all'",
|
|
303
|
+
checks: "true, false or a list of commands"
|
|
304
|
+
};
|
|
305
|
+
function distance(a, b) {
|
|
306
|
+
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
307
|
+
for (let i = 1; i <= a.length; i++) {
|
|
308
|
+
let prev = row[0] ?? 0;
|
|
309
|
+
row[0] = i;
|
|
310
|
+
for (let j = 1; j <= b.length; j++) {
|
|
311
|
+
const here = row[j] ?? 0;
|
|
312
|
+
row[j] = Math.min(here + 1, (row[j - 1] ?? 0) + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
313
|
+
prev = here;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return row[b.length] ?? 0;
|
|
317
|
+
}
|
|
318
|
+
const nearest = (key, keys) => keys.find((k) => distance(key.toLowerCase(), k.toLowerCase()) <= 2);
|
|
319
|
+
const shown = (value) => JSON.stringify(value) ?? String(value);
|
|
320
|
+
/**
|
|
321
|
+
* The first problem with a judge options object, as one sentence, or undefined. The checks run in
|
|
322
|
+
* the order each site has always run them, so a value with several problems names the same one.
|
|
323
|
+
*/
|
|
324
|
+
function judgeProblem(judge, site) {
|
|
325
|
+
/** A judge field and where it was set: `judge.threshold in evals/a.eval.ts › e`. */
|
|
326
|
+
const field = (key) => {
|
|
327
|
+
if (site.at === "call") return `c.judge() ${key ?? "options"}`;
|
|
328
|
+
const base = site.at === "config" ? site.path : "judge";
|
|
329
|
+
return `${key ? `${base}.${key}` : base} in ${site.at === "config" ? site.file : site.where}`;
|
|
330
|
+
};
|
|
331
|
+
const loc = site.at === "call" ? " in c.judge()" : site.at === "options" ? ` in ${site.where}` : ` in ${site.path === "judge" ? "" : `${site.path.replace(/\.judge$/, "")} in `}${site.file}`;
|
|
332
|
+
if (typeof judge !== "object" || judge === null || Array.isArray(judge)) return `${field()} must be an object such as { threshold: 0.8 } (got ${shown(judge)})`;
|
|
333
|
+
const value = judge;
|
|
334
|
+
const model = value.model;
|
|
335
|
+
if (site.at === "config" && typeof model === "string") {
|
|
336
|
+
const problem = checkModelString(model, field("model"));
|
|
337
|
+
if (problem) return problem;
|
|
338
|
+
}
|
|
339
|
+
const schema = site.at === "call" ? judgeCallSchema : site.at === "config" ? configJudgeDataSchema : judgeOptionsSchema;
|
|
340
|
+
const parsed = schema.safeParse(judge);
|
|
341
|
+
if (parsed.success) return void 0;
|
|
342
|
+
const numericFake = site.at === "config" && typeof model === "object" && model?.kind === "fake-judge" && typeof model.score === "number";
|
|
343
|
+
const issues = parsed.error.issues.filter((i) => !(numericFake && i.path[0] === "model"));
|
|
344
|
+
if (!issues.length) return void 0;
|
|
345
|
+
const at = (key) => issues.some((i) => i.path[0] === key);
|
|
346
|
+
const order = [];
|
|
347
|
+
const modelProblem = () => {
|
|
348
|
+
if (site.at !== "config" || !at("model")) return void 0;
|
|
349
|
+
if (typeof model === "object" && model?.kind === "fake-judge") return `${field("model")} must be fakeJudge({ score }) with a number. Pass a score function per group, eval or call instead.`;
|
|
350
|
+
return `${field("model")} must be a "provider/model" string or fakeJudge(). Pass a model object per group, eval or call instead.`;
|
|
351
|
+
};
|
|
352
|
+
const maxConcurrentProblem = () => {
|
|
353
|
+
if (site.at === "config" || !at("maxConcurrent")) return void 0;
|
|
354
|
+
return `${field("maxConcurrent")} must be a whole number of at least 1 (got ${shown(value.maxConcurrent)})`;
|
|
355
|
+
};
|
|
356
|
+
const thresholdProblem = () => {
|
|
357
|
+
if (!at("threshold")) return void 0;
|
|
358
|
+
return `${field("threshold")} must be between 0 and 1 (got ${shown(value.threshold)})`;
|
|
359
|
+
};
|
|
360
|
+
const keyProblem = () => {
|
|
361
|
+
const name = issues.find((i) => i.code === "unrecognized_keys" && i.path.length === 0)?.keys[0];
|
|
362
|
+
if (name === void 0) return void 0;
|
|
363
|
+
const allowed = Object.keys(schema.shape);
|
|
364
|
+
const hint = JUDGE_HINTS[name] ?? (STATE_KEYS.includes(name) ? `state.${name}` : nearest(name, allowed));
|
|
365
|
+
const why = site.at !== "call" && (name === "on" || name === "artifacts") ? ` ${name} is per call: c.judge(question, { ${name} }).` : hint ? ` Did you mean "${hint}"?` : "";
|
|
366
|
+
return `Unknown judge option "${name}"${loc}.${why}`;
|
|
367
|
+
};
|
|
368
|
+
const stateProblem = () => {
|
|
369
|
+
const issue = issues.find((i) => i.path[0] === "state");
|
|
370
|
+
if (!issue) return void 0;
|
|
371
|
+
const { state } = value;
|
|
372
|
+
if (typeof state !== "object" || state === null || Array.isArray(state)) return `${field("state")} must be an object such as { transcript: 'tools' } (got ${shown(state)})`;
|
|
373
|
+
if (issue.code === "unrecognized_keys" && issue.path.length === 1) {
|
|
374
|
+
const key = issue.keys[0] ?? "";
|
|
375
|
+
const hint = nearest(key, STATE_KEYS);
|
|
376
|
+
return `Unknown judge.state key "${key}"${loc}.${hint ? ` Did you mean "${hint}"?` : ""}`;
|
|
377
|
+
}
|
|
378
|
+
const key = issue.path[1];
|
|
379
|
+
return `${field(`state.${String(key)}`)} must be ${VALUES[key]} (got ${shown(state[key])})`;
|
|
380
|
+
};
|
|
381
|
+
const providerOptionsProblem = () => at("providerOptions") ? `${field("providerOptions")} must map each provider to an object of JSON values, e.g. { google: { thinkingConfig: { thinkingBudget: 0 } } } (got ${shown(value.providerOptions)})` : void 0;
|
|
382
|
+
const artifactsProblem = () => at("artifacts") ? `c.judge() artifacts ${badJudgeArtifacts(value.artifacts)}` : void 0;
|
|
383
|
+
if (site.at === "config") order.push(modelProblem, keyProblem, stateProblem, providerOptionsProblem, thresholdProblem);
|
|
384
|
+
else if (site.at === "call") order.push(thresholdProblem, maxConcurrentProblem, keyProblem, stateProblem, providerOptionsProblem, artifactsProblem);
|
|
385
|
+
else order.push(maxConcurrentProblem, thresholdProblem, keyProblem, stateProblem, providerOptionsProblem);
|
|
386
|
+
for (const check of order) {
|
|
387
|
+
const problem = check();
|
|
388
|
+
if (problem) return problem;
|
|
389
|
+
}
|
|
390
|
+
return `judge options${loc} aren't valid: ${issues[0]?.message ?? "unknown problem"}`;
|
|
391
|
+
}
|
|
392
|
+
//#endregion
|
|
393
|
+
//#region src/harness/effort.ts
|
|
394
|
+
/**
|
|
395
|
+
* The reasoning effort a harness asked for. Same user-facing name on every factory; the CLI's
|
|
396
|
+
* native flag may say thinking. Undefined when the factory left it unset. Fast is not this.
|
|
397
|
+
*/
|
|
398
|
+
function harnessEffort(h) {
|
|
399
|
+
return "effort" in h && typeof h.effort === "string" ? h.effort : void 0;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* The reasoning effort a served-model label recorded, e.g. `gpt-5.6-luna[effort=medium]` →
|
|
403
|
+
* `medium`. Fast (`[fast=true]`) is not effort and is ignored.
|
|
404
|
+
*/
|
|
405
|
+
function effortFromModelLabel(model) {
|
|
406
|
+
const inner = model?.match(/\[([^\]]+)]/)?.[1];
|
|
407
|
+
if (!inner) return void 0;
|
|
408
|
+
for (const part of inner.split(",")) {
|
|
409
|
+
const eq = part.indexOf("=");
|
|
410
|
+
if (eq < 0) continue;
|
|
411
|
+
const key = part.slice(0, eq).trim();
|
|
412
|
+
if (key === "fast") continue;
|
|
413
|
+
if (/^(?:reasoning_)?effort$|^thinking$/.test(key)) {
|
|
414
|
+
const value = part.slice(eq + 1).trim();
|
|
415
|
+
if (value) return value;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/** The first reasoning-effort suffix among served-model labels; undefined when none name one. */
|
|
420
|
+
function servedEffortOf(models) {
|
|
421
|
+
if (!models) return void 0;
|
|
422
|
+
for (const model of models) {
|
|
423
|
+
const effort = effortFromModelLabel(model);
|
|
424
|
+
if (effort) return effort;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/** Unique reasoning-effort suffixes among served-model labels, in first-seen order. */
|
|
428
|
+
function resolvedEffortOf(models) {
|
|
429
|
+
if (!models) return [];
|
|
430
|
+
const seen = /* @__PURE__ */ new Set();
|
|
431
|
+
const out = [];
|
|
432
|
+
for (const model of models) {
|
|
433
|
+
const effort = effortFromModelLabel(model);
|
|
434
|
+
if (effort && !seen.has(effort)) {
|
|
435
|
+
seen.add(effort);
|
|
436
|
+
out.push(effort);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return out;
|
|
440
|
+
}
|
|
441
|
+
//#endregion
|
|
442
|
+
//#region src/judge/state-options.ts
|
|
443
|
+
const DEFAULT_STATE = {
|
|
444
|
+
task: true,
|
|
445
|
+
output: true,
|
|
446
|
+
files: true,
|
|
447
|
+
diff: true,
|
|
448
|
+
contents: [],
|
|
449
|
+
transcript: false,
|
|
450
|
+
turns: "last",
|
|
451
|
+
checks: false
|
|
452
|
+
};
|
|
453
|
+
/** Every key set, in `STATE_KEYS` order, so equal selections serialise the same. */
|
|
454
|
+
function resolveStateOptions(state) {
|
|
455
|
+
const out = {};
|
|
456
|
+
for (const key of STATE_KEYS) out[key] = state?.[key] ?? DEFAULT_STATE[key];
|
|
457
|
+
return out;
|
|
458
|
+
}
|
|
459
|
+
/** `inner` over `outer`, field by field, and `state` key by key. */
|
|
460
|
+
function mergeJudge(outer, inner) {
|
|
461
|
+
if (!inner) return outer;
|
|
462
|
+
const out = {
|
|
463
|
+
...outer,
|
|
464
|
+
...inner
|
|
465
|
+
};
|
|
466
|
+
if (outer.state || inner.state) out.state = {
|
|
467
|
+
...outer.state,
|
|
468
|
+
...inner.state
|
|
469
|
+
};
|
|
470
|
+
return out;
|
|
471
|
+
}
|
|
472
|
+
//#endregion
|
|
473
|
+
//#region src/options.ts
|
|
474
|
+
const KNOWN = [
|
|
475
|
+
"harness",
|
|
476
|
+
"sandbox",
|
|
477
|
+
"environment",
|
|
478
|
+
"compare",
|
|
479
|
+
"runs",
|
|
480
|
+
"gate",
|
|
481
|
+
"env",
|
|
482
|
+
"egress",
|
|
483
|
+
"judge",
|
|
484
|
+
"capture",
|
|
485
|
+
"question",
|
|
486
|
+
"timeout",
|
|
487
|
+
"tags",
|
|
488
|
+
"pin"
|
|
489
|
+
];
|
|
490
|
+
const SUGGEST = {
|
|
491
|
+
harnesses: "harness",
|
|
492
|
+
sandboxes: "sandbox",
|
|
493
|
+
environments: "environment",
|
|
494
|
+
baseline: "environment",
|
|
495
|
+
variants: "compare",
|
|
496
|
+
repeat: "runs",
|
|
497
|
+
repeats: "runs",
|
|
498
|
+
iterations: "runs",
|
|
499
|
+
trials: "runs",
|
|
500
|
+
secrets: "env",
|
|
501
|
+
vars: "env",
|
|
502
|
+
network: "egress",
|
|
503
|
+
networkPolicy: "egress",
|
|
504
|
+
tag: "tags",
|
|
505
|
+
pins: "pin",
|
|
506
|
+
passRate: "gate",
|
|
507
|
+
threshold: "gate"
|
|
508
|
+
};
|
|
509
|
+
/** Throws unless `value` is `'all'` or `{ passRate }` with a rate in (0, 1]. */
|
|
510
|
+
function checkGate(value, where) {
|
|
511
|
+
const problem = gateProblem(value, where);
|
|
512
|
+
if (problem) throw new UsageError(problem);
|
|
513
|
+
}
|
|
514
|
+
const PINNABLE = [
|
|
515
|
+
"harness",
|
|
516
|
+
"runs",
|
|
517
|
+
"compare"
|
|
518
|
+
];
|
|
519
|
+
const ID = /^[A-Za-z0-9._:@+\-=[\]]+$/;
|
|
520
|
+
const sanitize = (text) => (text.split("/").at(-1) ?? text).replace(/[^A-Za-z0-9._:@-]+/g, "-");
|
|
521
|
+
function harnessId(h) {
|
|
522
|
+
if (h.id) return h.id;
|
|
523
|
+
const effort = harnessEffort(h);
|
|
524
|
+
const suffix = effort ? `[effort=${sanitize(effort)}]` : "";
|
|
525
|
+
if (h.kind === "fake") return `fake${suffix}`;
|
|
526
|
+
return `${h.model ? `${h.kind}:${sanitize(h.model)}` : h.kind}${suffix}`;
|
|
527
|
+
}
|
|
528
|
+
function installId(install) {
|
|
529
|
+
if (!install?.length) return "";
|
|
530
|
+
const key = JSON.stringify(install.map((step) => typeof step === "string" ? step : {
|
|
531
|
+
run: step.run,
|
|
532
|
+
timeout: step.timeout
|
|
533
|
+
}));
|
|
534
|
+
return `+${createHash("sha256").update(key).digest("hex").slice(0, 8)}`;
|
|
535
|
+
}
|
|
536
|
+
/** The provider a sandbox runs on: its kind, or `podman` for a Docker sandbox on Podman. */
|
|
537
|
+
const sandboxProvider = (s) => s.kind === "docker" && s.engine === "podman" ? "podman" : s.kind;
|
|
538
|
+
function sandboxId(s) {
|
|
539
|
+
if (s.id) return s.id;
|
|
540
|
+
if (s.kind === "fake") return "fake";
|
|
541
|
+
const install = s.kind === "docker" ? installId(s.install) : "";
|
|
542
|
+
const engine = s.engine ?? "docker";
|
|
543
|
+
if (s.dockerfile) {
|
|
544
|
+
const [file = "", dir = ""] = s.dockerfile.split(/[\\/]/).reverse();
|
|
545
|
+
return `${engine}:${sanitize(file === "Dockerfile" ? dir || file : file)}${install}`;
|
|
546
|
+
}
|
|
547
|
+
const image = s.image ?? s.from;
|
|
548
|
+
return image ? `${engine}:${sanitize(image)}${install}` : `${engine}${install}`;
|
|
549
|
+
}
|
|
550
|
+
const cellKey = (harness, environment, sandbox) => `${harness}/${environment}/${sandbox}`;
|
|
551
|
+
const isHarness = (v) => isObjectLike(v) && (v.kind === "fake" || isAgentKind(v.kind));
|
|
552
|
+
function checkMaxConcurrent(value, where) {
|
|
553
|
+
if (value === void 0 || typeof value === "number" && Number.isInteger(value) && value >= 1) return;
|
|
554
|
+
throw new UsageError(`${where} must be a whole number of at least 1 (got ${JSON.stringify(value)})`);
|
|
555
|
+
}
|
|
556
|
+
const describe = (value) => {
|
|
557
|
+
if (Array.isArray(value)) return `a list of ${value.length}`;
|
|
558
|
+
if (typeof value === "object" && value !== null) {
|
|
559
|
+
const keys = Object.keys(value);
|
|
560
|
+
return keys.length ? `an object with ${keys.map((k) => `"${k}"`).join(", ")}` : "an object";
|
|
561
|
+
}
|
|
562
|
+
return JSON.stringify(value) ?? String(value);
|
|
563
|
+
};
|
|
564
|
+
/** `compare: false` and `environment` beside `compare.baseline` are never legal options. */
|
|
565
|
+
function refuseIllegalCompare(options, where) {
|
|
566
|
+
if (options.compare === false) throw new UsageError(`compare: false can't be set in ${where}. A profile turns compare off with limit.compare: false, which runs the baseline and strips the variants. On a group or eval, omit compare or pass compare: { baseline, variants }.`);
|
|
567
|
+
const baseline = options.compare !== null && typeof options.compare === "object" && "baseline" in options.compare ? options.compare.baseline : void 0;
|
|
568
|
+
if (options.environment !== void 0 && baseline !== void 0) throw new UsageError(`environment and compare.baseline can't both be set in ${where}. compare: { baseline, variants } names every arm, so drop environment, or drop baseline to make environment the control.`);
|
|
569
|
+
}
|
|
570
|
+
/** Throws on keys that aren't options, and on values of the wrong shape, suggesting a fix. */
|
|
571
|
+
function checkOptions(options, where, allowed = KNOWN) {
|
|
572
|
+
for (const key of Object.keys(options)) {
|
|
573
|
+
if (allowed.includes(key)) continue;
|
|
574
|
+
const near = SUGGEST[key] ?? closest(key, allowed);
|
|
575
|
+
const hint = key === "pool" ? " pool is an option of evaluate.each, beside the cases: evaluate.each(cases, { pool: false })." : near ? ` Did you mean "${near}"?` : "";
|
|
576
|
+
throw new UsageError(`Unknown option "${key}" in ${where}.${hint}`);
|
|
577
|
+
}
|
|
578
|
+
const o = options;
|
|
579
|
+
if (o.runs !== void 0 && !(Number.isInteger(o.runs) && o.runs >= 1)) throw new UsageError(`runs in ${where} must be a whole number of at least 1 (got ${JSON.stringify(o.runs)})`);
|
|
580
|
+
if (o.gate !== void 0) checkGate(o.gate, where);
|
|
581
|
+
if (o.tags !== void 0 && (!Array.isArray(o.tags) || o.tags.some((t) => typeof t !== "string"))) throw new UsageError(`tags in ${where} must be a list of strings, e.g. tags: ['smoke'] (got ${JSON.stringify(o.tags)})`);
|
|
582
|
+
if (o.env !== void 0) checkEnv(o.env, where, UsageError);
|
|
583
|
+
if (o.egress !== void 0) checkEgress(o.egress, where, UsageError);
|
|
584
|
+
const judgeIssue = o.judge !== void 0 && judgeProblem(o.judge, {
|
|
585
|
+
at: "options",
|
|
586
|
+
where
|
|
587
|
+
});
|
|
588
|
+
if (judgeIssue) throw new UsageError(judgeIssue);
|
|
589
|
+
if (o.sandbox !== void 0) {
|
|
590
|
+
const kind = o.sandbox.kind;
|
|
591
|
+
if (kind !== "docker" && kind !== "fake") throw new UsageError(`sandbox in ${where} must come from docker(), podman() or fakeSandbox() (got ${describe(o.sandbox)})`);
|
|
592
|
+
const max = o.sandbox.maxConcurrent;
|
|
593
|
+
if (max !== void 0 && !(Number.isInteger(max) && max >= 1)) throw new UsageError(`sandbox.maxConcurrent in ${where} must be a whole number of at least 1 (got ${String(max)})`);
|
|
594
|
+
const problem = sandboxProblem(o.sandbox);
|
|
595
|
+
if (problem) throw new UsageError(`sandbox in ${where}: ${problem}`);
|
|
596
|
+
}
|
|
597
|
+
if (o.environment !== void 0) {
|
|
598
|
+
if (Array.isArray(o.environment)) throw new UsageError(`environment takes one environment, not a list (${where}). To compare environments, use compare: { baseline, variants: [a, b] }. baseline is the control; variants are not layered on it.`);
|
|
599
|
+
if (!isEnvironment(o.environment)) throw new UsageError(`environment in ${where} must come from defineEnvironment(…) (got ${describe(o.environment)})`);
|
|
600
|
+
const problem = environmentDataProblem(o.environment, "environment", where);
|
|
601
|
+
if (problem) throw new UsageError(problem);
|
|
602
|
+
}
|
|
603
|
+
refuseIllegalCompare(options, where);
|
|
604
|
+
if (o.compare !== void 0) {
|
|
605
|
+
const problem = compareProblem(o.compare, where, z.custom(isEnvironment));
|
|
606
|
+
if (problem) throw new UsageError(problem);
|
|
607
|
+
const { baseline, variants } = o.compare;
|
|
608
|
+
for (const [label, env] of [["compare.baseline", baseline], ...variants.map((v, i) => [`compare.variants[${i}]`, v])]) {
|
|
609
|
+
const bad = env && environmentDataProblem(env, label, where);
|
|
610
|
+
if (bad) throw new UsageError(bad);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
if (o.pin !== void 0) {
|
|
614
|
+
const bad = Array.isArray(o.pin) ? o.pin.filter((k) => !PINNABLE.includes(k)) : [o.pin];
|
|
615
|
+
if (bad.length) throw new UsageError(`pin in ${where} lists harness, runs and/or compare (got ${bad.map((k) => JSON.stringify(k)).join(", ")})`);
|
|
616
|
+
}
|
|
617
|
+
if (o.capture !== void 0) checkCapture(o.capture, where);
|
|
618
|
+
const questionIssue = o.question !== void 0 && questionOptionsProblem(o.question, where);
|
|
619
|
+
if (questionIssue) throw new UsageError(questionIssue);
|
|
620
|
+
}
|
|
621
|
+
const CAPTURE_KEYS = [
|
|
622
|
+
"viewport",
|
|
623
|
+
"timeout",
|
|
624
|
+
"browser",
|
|
625
|
+
"model"
|
|
626
|
+
];
|
|
627
|
+
const CAPTURE_SHAPE = "{ viewport?, timeout?, browser?, model? }";
|
|
628
|
+
const CAPTURE_CALL_ONLY = /* @__PURE__ */ new Set([
|
|
629
|
+
"instructions",
|
|
630
|
+
"wait",
|
|
631
|
+
"name",
|
|
632
|
+
"path"
|
|
633
|
+
]);
|
|
634
|
+
/** Throws unless `capture` is a shallow-merge object with a viewport, timeout, browser and/or model. */
|
|
635
|
+
function checkCapture(value, where) {
|
|
636
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new UsageError(`capture in ${where} is ${CAPTURE_SHAPE} (got ${JSON.stringify(value)})`);
|
|
637
|
+
const o = value;
|
|
638
|
+
if (o.instructions !== void 0) throw new UsageError(`capture.instructions in ${where} can't be set: instructions go on the call, c.capture(port, { instructions }).`);
|
|
639
|
+
const unknown = Object.keys(o).filter((k) => !CAPTURE_KEYS.includes(k));
|
|
640
|
+
if (unknown.length) {
|
|
641
|
+
const key = unknown[0] ?? "";
|
|
642
|
+
if (key === "use" || key === "fake") throw new UsageError(`Unknown capture key "${key}" in ${where}. Set capture.model; tests pass fakeInstructions from reeval/testing as instructions.`);
|
|
643
|
+
if (CAPTURE_CALL_ONLY.has(key)) throw new UsageError(`capture.${key} in ${where} can't be set: ${key} goes on the call, c.capture(port, { ${key} }). Use viewport, timeout, browser, model.`);
|
|
644
|
+
throw new UsageError(`Unknown capture key "${key}" in ${where}. Use viewport, timeout, browser, model.`);
|
|
645
|
+
}
|
|
646
|
+
if (o.viewport !== void 0) checkViewport(o.viewport, where);
|
|
647
|
+
if (o.timeout !== void 0) parseDuration(o.timeout);
|
|
648
|
+
if (o.browser !== void 0 && typeof o.browser !== "boolean") throw new UsageError(`capture.browser in ${where} is true or false (got ${JSON.stringify(o.browser)})`);
|
|
649
|
+
if (o.model !== void 0) checkCaptureModel(o.model, where);
|
|
650
|
+
}
|
|
651
|
+
function checkViewport(value, where) {
|
|
652
|
+
if (value === "full-page") return;
|
|
653
|
+
if (!isObjectLike(value) || Array.isArray(value)) throw new UsageError(`capture.viewport in ${where} is { width, height } or 'full-page' (got ${JSON.stringify(value)})`);
|
|
654
|
+
const keys = Object.keys(value);
|
|
655
|
+
if (keys.includes("fullPage")) throw new UsageError(`capture.viewport in ${where} is { width, height } or 'full-page', not { height, fullPage }. Use viewport: 'full-page' for a tall shot.`);
|
|
656
|
+
const isSize = (n) => typeof n === "number" && Number.isInteger(n) && n > 0;
|
|
657
|
+
if (keys.some((k) => k !== "width" && k !== "height") || !isSize(value.width) || !isSize(value.height)) throw new UsageError(`capture.viewport in ${where} is { width, height } or 'full-page' (got ${JSON.stringify(value)})`);
|
|
658
|
+
}
|
|
659
|
+
function checkCaptureModel(value, where) {
|
|
660
|
+
if (typeof value !== "string" || !value.includes("/")) throw new UsageError(`capture.model in ${where} is a 'provider/model' string, e.g. 'google/gemini-3.8-flash' (got ${JSON.stringify(value)})`);
|
|
661
|
+
if (isEvaluationModel(value)) throw new UsageError(`capture.model "${value}" in ${where} is an evaluation model: it scores yes/no questions and can't drive a capture. Use a language model that accepts images, e.g. 'google/gemini-3.8-flash'.`);
|
|
662
|
+
}
|
|
663
|
+
function applyHarness(inherited, value, where) {
|
|
664
|
+
if (value === void 0) return inherited;
|
|
665
|
+
const next = typeof value === "function" ? value(inherited) : isHarnessEdit(value) ? editHarness(inherited, value, where) : value;
|
|
666
|
+
const list = Array.isArray(next) ? next : [next];
|
|
667
|
+
if (list.length === 0) throw new UsageError(`harness is an empty list in ${where}. Give at least one, e.g. harness: ${HARNESS_EXAMPLE}`);
|
|
668
|
+
checkHarnesses(list, where);
|
|
669
|
+
return list;
|
|
670
|
+
}
|
|
671
|
+
const isHarnessEdit = (v) => typeof v === "object" && v !== null && !Array.isArray(v) && !("kind" in v) && ("add" in v || "only" in v);
|
|
672
|
+
/** `{ add }` appends to the inherited harnesses; `{ only }` keeps those with these ids. */
|
|
673
|
+
function editHarness(inherited, edit, where) {
|
|
674
|
+
if ("add" in edit && "only" in edit) throw new UsageError(`harness in ${where} takes { add } or { only }, not both. Use add to append to the inherited harnesses, only to keep some of them.`);
|
|
675
|
+
if ("add" in edit) return [...inherited, ...Array.isArray(edit.add) ? edit.add : [edit.add]];
|
|
676
|
+
const ids = Array.isArray(edit.only) ? edit.only : [edit.only];
|
|
677
|
+
const have = inherited.map(harnessId);
|
|
678
|
+
const missing = ids.filter((id) => typeof id !== "string" || !have.includes(id));
|
|
679
|
+
if (missing.length) throw new UsageError(`harness { only: ${missing.map((id) => JSON.stringify(id)).join(", ")} } in ${where} names no inherited harness (inherited: ${have.join(", ")})`);
|
|
680
|
+
return inherited.filter((h) => ids.includes(harnessId(h)));
|
|
681
|
+
}
|
|
682
|
+
/** Each harness is one, with a valid `maxConcurrent`. */
|
|
683
|
+
function checkHarnesses(list, where) {
|
|
684
|
+
for (const item of list) {
|
|
685
|
+
if (!isHarness(item)) throw new UsageError(`harness in ${where} must be like ${HARNESS_EXAMPLE} (got ${describe(item)})`);
|
|
686
|
+
if (item.kind === "opencode") {
|
|
687
|
+
const why = opencodeModelError(item.model);
|
|
688
|
+
if (why) throw new UsageError(`opencode() in ${where}: ${why}`);
|
|
689
|
+
}
|
|
690
|
+
checkMaxConcurrent(item.maxConcurrent, `harness.maxConcurrent in ${where}`);
|
|
691
|
+
const { startTimeout } = item;
|
|
692
|
+
if (startTimeout !== void 0) try {
|
|
693
|
+
parseDuration(startTimeout);
|
|
694
|
+
} catch (error) {
|
|
695
|
+
throw new UsageError(`harness.startTimeout in ${where}: ${error.message}`);
|
|
696
|
+
}
|
|
697
|
+
const problem = harnessProblem(item);
|
|
698
|
+
if (problem) throw new UsageError(`harness in ${where}: ${problem}`);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
function checkUnique(list, where) {
|
|
702
|
+
const seen = /* @__PURE__ */ new Set();
|
|
703
|
+
for (const item of list) {
|
|
704
|
+
const key = harnessId(item);
|
|
705
|
+
if (!ID.test(key)) throw new UsageError(`The harness id "${key}" in ${where} may only use letters, digits and . _ : @ + - [ ] =`);
|
|
706
|
+
if (seen.has(key)) throw new UsageError(`Two harnesses in ${where} resolve to the id "${key}". Give one an id, e.g. cursor({ model: '…', id: '…' })`);
|
|
707
|
+
seen.add(key);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
/** A question's own `across`, else the one `compare.across` names when it names exactly one. */
|
|
711
|
+
const plannedAcross = (own, compare) => {
|
|
712
|
+
if (own) return own;
|
|
713
|
+
const list = compare === void 0 ? void 0 : Array.isArray(compare) ? compare : [compare];
|
|
714
|
+
return list?.length === 1 ? list[0] : void 0;
|
|
715
|
+
};
|
|
716
|
+
const plannedQuestions = (value, compareAcross) => (value ?? []).map((q) => {
|
|
717
|
+
if (typeof q === "string") {
|
|
718
|
+
const across = plannedAcross(void 0, compareAcross);
|
|
719
|
+
return across ? {
|
|
720
|
+
question: q,
|
|
721
|
+
across
|
|
722
|
+
} : { question: q };
|
|
723
|
+
}
|
|
724
|
+
const across = plannedAcross(q.across, compareAcross);
|
|
725
|
+
return {
|
|
726
|
+
question: q.question,
|
|
727
|
+
...q.model !== void 0 ? { model: q.model } : {},
|
|
728
|
+
...across ? { across } : {},
|
|
729
|
+
...q.state ? { state: q.state } : {},
|
|
730
|
+
...q.artifacts !== void 0 ? { artifacts: q.artifacts } : {}
|
|
731
|
+
};
|
|
732
|
+
});
|
|
733
|
+
function normalizeCompare(value) {
|
|
734
|
+
if (value === void 0) return void 0;
|
|
735
|
+
return {
|
|
736
|
+
...value.baseline ? { baseline: value.baseline } : {
|
|
737
|
+
baseline: emptyEnvironment(EMPTY_BASELINE),
|
|
738
|
+
from: "empty"
|
|
739
|
+
},
|
|
740
|
+
variants: value.variants,
|
|
741
|
+
gate: value.gate ?? "variants",
|
|
742
|
+
...value.minLift !== void 0 ? { minLift: value.minLift } : {},
|
|
743
|
+
...value.harness ? { harness: value.harness.map((h) => typeof h === "string" ? h : harnessId(h)) } : {},
|
|
744
|
+
of: value.variants.length,
|
|
745
|
+
questions: plannedQuestions(value.questions, value.across)
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
function applyLevel(out, options, where, egress = "own") {
|
|
749
|
+
for (const [key, value] of Object.entries(options)) if (value !== void 0) out.sources = {
|
|
750
|
+
...out.sources,
|
|
751
|
+
[key]: where
|
|
752
|
+
};
|
|
753
|
+
if (options.egress !== void 0) out.egress[egress] = options.egress;
|
|
754
|
+
out.harness = applyHarness(out.harness, options.harness, where);
|
|
755
|
+
if (options.sandbox !== void 0) out.sandbox = options.sandbox;
|
|
756
|
+
if (options.environment !== void 0) out.environment = options.environment;
|
|
757
|
+
if (options.compare !== void 0) out.compare = normalizeCompare(options.compare);
|
|
758
|
+
if (options.runs !== void 0) out.runs = options.runs;
|
|
759
|
+
if (options.gate !== void 0) out.gate = options.gate;
|
|
760
|
+
if (options.timeout !== void 0) out.timeout = options.timeout;
|
|
761
|
+
Object.assign(out.env, options.env);
|
|
762
|
+
out.judge = mergeJudge(out.judge, options.judge);
|
|
763
|
+
if (options.capture !== void 0) {
|
|
764
|
+
const { viewport, ...rest } = options.capture;
|
|
765
|
+
out.capture = {
|
|
766
|
+
...out.capture,
|
|
767
|
+
...rest
|
|
768
|
+
};
|
|
769
|
+
if (viewport !== void 0) out.capture.viewport = viewport;
|
|
770
|
+
}
|
|
771
|
+
out.question = mergeQuestion(out.question, options.question);
|
|
772
|
+
for (const tag of options.tags ?? []) if (!out.tags.includes(tag)) out.tags.push(tag);
|
|
773
|
+
for (const key of options.pin ?? []) if (!out.pin.includes(key)) out.pin.push(key);
|
|
774
|
+
}
|
|
775
|
+
/** What a profile sets over the config's judge: its `judge`, then its `defaults.judge`. */
|
|
776
|
+
const profileJudge = (profile) => mergeJudge(profile.judge, profile.defaults.judge);
|
|
777
|
+
/**
|
|
778
|
+
* Config, then the profile's defaults, then each enclosing group, then the eval; then the
|
|
779
|
+
* profile's limit (narrowing only, except pinned keys); then `--runs`.
|
|
780
|
+
*/
|
|
781
|
+
function cascade(base, levels, where, options = {}) {
|
|
782
|
+
const out = {
|
|
783
|
+
...base,
|
|
784
|
+
env: { ...base.env },
|
|
785
|
+
egress: { ...base.egress },
|
|
786
|
+
judge: { ...base.judge },
|
|
787
|
+
capture: { ...base.capture },
|
|
788
|
+
question: { ...base.question },
|
|
789
|
+
tags: [...base.tags],
|
|
790
|
+
pin: [...base.pin]
|
|
791
|
+
};
|
|
792
|
+
const { profile } = options;
|
|
793
|
+
const notes = [];
|
|
794
|
+
if (profile) applyLevel(out, {
|
|
795
|
+
...profile.defaults,
|
|
796
|
+
judge: profileJudge(profile)
|
|
797
|
+
}, `profiles.${profile.name}.defaults`, "inherited");
|
|
798
|
+
for (const { options: own, where: at } of levels) applyLevel(out, own, at);
|
|
799
|
+
if (out.compare?.from && out.environment) out.compare = {
|
|
800
|
+
...out.compare,
|
|
801
|
+
baseline: out.environment,
|
|
802
|
+
from: "environment"
|
|
803
|
+
};
|
|
804
|
+
if (out.compare?.questions.length) {
|
|
805
|
+
const questions = out.compare.questions.map((q) => withQuestionDefaults(q, out.question));
|
|
806
|
+
const blank = questions.find(showsNothing);
|
|
807
|
+
if (blank) throw new UsageError(`compare.questions "${blank.question}" in ${where} shows the model nothing: every state key is off and it sends no artifacts. Turn on a state key, such as state: { diff: true }, or send artifacts.`);
|
|
808
|
+
out.compare = {
|
|
809
|
+
...out.compare,
|
|
810
|
+
questions
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
checkUnique(out.harness, where);
|
|
814
|
+
if (profile?.defaults.harness !== void 0 && levels.some((l) => l.options.harness)) notes.push("harness set by the eval");
|
|
815
|
+
const ids = out.harness.map(harnessId);
|
|
816
|
+
const missing = (out.compare?.harness)?.filter((id) => !ids.includes(id)) ?? [];
|
|
817
|
+
if (missing.length) throw new UsageError(`compare.harness lists ${missing.join(", ")}, which ${where} doesn't run (it runs ${ids.join(", ")}).`);
|
|
818
|
+
for (const v of out.compare?.variants ?? []) if (v.name === out.compare?.baseline.name) throw new UsageError(out.compare.from === "empty" ? `compare in ${where} lists a variant named ${v.name}, the name of the empty control a compare without a baseline or environment runs. Rename the variant, or set compare.baseline.` : out.compare.from === "environment" ? `compare in ${where} lists ${v.name}, which is the control: compare has no baseline, so the eval's environment is. List only the other environments.` : `compare in ${where} lists ${v.name}, which is the baseline. List only the other environments.`);
|
|
819
|
+
const names = out.compare?.variants.map((v) => v.name) ?? [];
|
|
820
|
+
const dupe = names.find((n, i) => names.indexOf(n) !== i);
|
|
821
|
+
if (dupe) throw new UsageError(`compare in ${where} lists two variants named ${dupe}. Give each variant its own name.`);
|
|
822
|
+
let skip;
|
|
823
|
+
const limit = profile?.limit;
|
|
824
|
+
if (profile && limit) {
|
|
825
|
+
const tag = `profile ${profile.name}`;
|
|
826
|
+
const pinned = (key) => out.pin.includes(key);
|
|
827
|
+
if (limit.runs !== void 0 && options.runs === void 0 && !pinned("runs") && out.runs > limit.runs) {
|
|
828
|
+
notes.push(`runs ${out.runs} → ${limit.runs} (${tag})`);
|
|
829
|
+
out.runs = limit.runs;
|
|
830
|
+
}
|
|
831
|
+
if (limit.harness && !pinned("harness")) {
|
|
832
|
+
const kept = out.harness.filter((h) => limit.harness?.includes(harnessId(h)));
|
|
833
|
+
if (kept.length === 0) skip = `no harness left: ${tag} allows ${limit.harness.join(", ") || "none"}`;
|
|
834
|
+
else if (kept.length < out.harness.length) {
|
|
835
|
+
notes.push(`harnesses ${out.harness.length} → ${kept.length} (${tag})`);
|
|
836
|
+
out.harness = kept;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
if (out.compare && limit.compare !== void 0 && !pinned("compare")) {
|
|
840
|
+
if (limit.compare === false) {
|
|
841
|
+
notes.push(`compare off (${tag})`);
|
|
842
|
+
out.compare = {
|
|
843
|
+
...out.compare,
|
|
844
|
+
variants: []
|
|
845
|
+
};
|
|
846
|
+
} else if (out.compare.variants.length > limit.compare.maxVariants) {
|
|
847
|
+
notes.push(`variants ${out.compare.variants.length} → ${limit.compare.maxVariants} (${tag})`);
|
|
848
|
+
out.compare = {
|
|
849
|
+
...out.compare,
|
|
850
|
+
variants: out.compare.variants.slice(0, limit.compare.maxVariants)
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
if (out.compare.variants.length === 0 && out.compare.questions.length) {
|
|
854
|
+
notes.push(`questions off (${tag})`);
|
|
855
|
+
out.compare = {
|
|
856
|
+
...out.compare,
|
|
857
|
+
questions: []
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
const pins = out.pin.filter((k) => k === "compare" ? limit.compare !== void 0 && out.compare : limit[k] !== void 0);
|
|
862
|
+
if (pins.length) notes.push(`pinned: ${pins.join(", ")}`);
|
|
863
|
+
}
|
|
864
|
+
if (options.runs !== void 0) out.runs = options.runs;
|
|
865
|
+
return {
|
|
866
|
+
resolved: out,
|
|
867
|
+
notes,
|
|
868
|
+
...skip ? { skip } : {}
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* The sandbox an attempt runs in: the resolved environment's (including an `.extend()`
|
|
873
|
+
* parent's), else the first variant's when this is an omitted-baseline empty control, else
|
|
874
|
+
* the cascaded option (`defineConfig` / profile defaults / group / eval), else implicit Docker.
|
|
875
|
+
* A named environment is a total override: the config environment's sandbox is not inherited.
|
|
876
|
+
*/
|
|
877
|
+
const attemptSandbox = (resolved, env) => {
|
|
878
|
+
const own = env.layers.findLast((l) => l.spec.sandbox)?.spec.sandbox;
|
|
879
|
+
if (own) return own;
|
|
880
|
+
const variant = resolved.compare?.from === "empty" && env.name === "baseline" ? resolved.compare.variants[0] : void 0;
|
|
881
|
+
return sandboxOf(variant?.layers ?? [], resolved.sandbox);
|
|
882
|
+
};
|
|
883
|
+
/**
|
|
884
|
+
* Which level set this attempt's sandbox: the cascade source of the environment that named one
|
|
885
|
+
* (an `.extend()` child still names that environment), else the first variant when this is an
|
|
886
|
+
* omitted-baseline empty control, else the cascaded `sandbox` option, else the config (implicit
|
|
887
|
+
* Docker).
|
|
888
|
+
*/
|
|
889
|
+
function sandboxSetBy(planned, resolved, configFile) {
|
|
890
|
+
if (!planned.environment.layers.some((l) => l.spec.sandbox)) {
|
|
891
|
+
if (resolved.compare?.from === "empty" && planned.cell.role === "control" && resolved.compare.variants[0]?.layers.some((l) => l.spec.sandbox)) return resolved.sources?.compare ?? configFile;
|
|
892
|
+
return resolved.sources?.sandbox ?? configFile;
|
|
893
|
+
}
|
|
894
|
+
if (resolved.compare) {
|
|
895
|
+
if (planned.cell.role === "control") {
|
|
896
|
+
if (resolved.compare.from === "environment") return resolved.sources?.environment ?? configFile;
|
|
897
|
+
if (resolved.compare.from === "empty") return configFile;
|
|
898
|
+
return resolved.sources?.compare ?? configFile;
|
|
899
|
+
}
|
|
900
|
+
return resolved.sources?.compare ?? configFile;
|
|
901
|
+
}
|
|
902
|
+
return resolved.sources?.environment ?? configFile;
|
|
903
|
+
}
|
|
904
|
+
/** Per harness: the control, then each variant it compares on; then run numbers. */
|
|
905
|
+
function expand(resolved) {
|
|
906
|
+
const { compare } = resolved;
|
|
907
|
+
const control = applied(compare ? compare.baseline : resolved.environment);
|
|
908
|
+
const policy = resolved.gate ?? "all";
|
|
909
|
+
const gateIf = (gated) => gated ? policy : "none";
|
|
910
|
+
const attempts = [];
|
|
911
|
+
for (const harness of resolved.harness) {
|
|
912
|
+
const hid = harnessId(harness);
|
|
913
|
+
const cells = [(() => {
|
|
914
|
+
const sandbox = attemptSandbox(resolved, control);
|
|
915
|
+
const sid = sandboxId(sandbox);
|
|
916
|
+
return {
|
|
917
|
+
env: control,
|
|
918
|
+
sandbox,
|
|
919
|
+
cell: {
|
|
920
|
+
key: cellKey(hid, control.name, sid),
|
|
921
|
+
harness: hid,
|
|
922
|
+
environment: control.name,
|
|
923
|
+
sandbox: sid,
|
|
924
|
+
role: "control"
|
|
925
|
+
},
|
|
926
|
+
gate: gateIf(!compare || compare.variants.length === 0 || compare.gate === "all")
|
|
927
|
+
};
|
|
928
|
+
})()];
|
|
929
|
+
if (compare && (!compare.harness || compare.harness.includes(hid))) for (const v of compare.variants) {
|
|
930
|
+
const env = applied(v);
|
|
931
|
+
const sandbox = attemptSandbox(resolved, env);
|
|
932
|
+
const sid = sandboxId(sandbox);
|
|
933
|
+
cells.push({
|
|
934
|
+
env,
|
|
935
|
+
sandbox,
|
|
936
|
+
cell: {
|
|
937
|
+
key: cellKey(hid, env.name, sid),
|
|
938
|
+
harness: hid,
|
|
939
|
+
environment: env.name,
|
|
940
|
+
sandbox: sid,
|
|
941
|
+
role: "variant",
|
|
942
|
+
variant: v.name
|
|
943
|
+
},
|
|
944
|
+
gate: gateIf(compare.gate !== "none")
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
for (const { env, sandbox, cell, gate } of cells) {
|
|
948
|
+
const axes = [resolved.harness.length > 1 ? hid : "", compare ? cell.role === "control" ? "control" : env.name : ""].filter(Boolean);
|
|
949
|
+
for (let n = 1; n <= resolved.runs; n++) {
|
|
950
|
+
const label = [axes.join(" · "), resolved.runs > 1 ? `#${n}` : ""].filter(Boolean).join(" ");
|
|
951
|
+
attempts.push({
|
|
952
|
+
cell,
|
|
953
|
+
harness,
|
|
954
|
+
sandbox,
|
|
955
|
+
environment: env,
|
|
956
|
+
egress: plannedEgress(resolved.egress, harness, resolved.env, env.layers),
|
|
957
|
+
gate,
|
|
958
|
+
...cell.role === "variant" && compare?.minLift !== void 0 ? { minLift: compare.minLift } : {},
|
|
959
|
+
n,
|
|
960
|
+
label
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
return attempts;
|
|
966
|
+
}
|
|
967
|
+
/** Warnings about what an eval's environments pull in, e.g. skills at a moving git ref. */
|
|
968
|
+
function environmentWarnings(attempts) {
|
|
969
|
+
const out = /* @__PURE__ */ new Set();
|
|
970
|
+
for (const a of attempts) {
|
|
971
|
+
const workspace = workspaceOf(a.environment.layers);
|
|
972
|
+
if (workspace && isMovingRef(workspace.ref)) {
|
|
973
|
+
const ref = workspace.ref ? `moving ref "${workspace.ref}"` : "no ref (the default branch)";
|
|
974
|
+
out.add(`workspace git(${workspace.repo}) uses ${ref}: results aren't comparable over time. Pin a tag or SHA (the resolved commit is recorded either way).`);
|
|
975
|
+
}
|
|
976
|
+
for (const layer of a.environment.layers) for (const s of layer.spec.skills ?? []) {
|
|
977
|
+
if (s.source === "git" && isMovingRef(s.ref)) {
|
|
978
|
+
const ref = s.ref ? `moving ref "${s.ref}"` : "no ref (the default branch)";
|
|
979
|
+
out.add(`skill.git(${s.repo}) uses ${ref}: results aren't comparable over time. Pin a tag or SHA (the resolved commit is recorded either way).`);
|
|
980
|
+
}
|
|
981
|
+
if (s.source === "registry" && !s.version) out.add(`skill.registry(${s.id}) has no version: results aren't comparable over time. Pin one (the resolved commit is recorded either way).`);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
return [...out];
|
|
985
|
+
}
|
|
986
|
+
/** Secrets a Docker attempt would get as real values, because they have no `hosts`. */
|
|
987
|
+
function unbrokered(env, attempts) {
|
|
988
|
+
const out = /* @__PURE__ */ new Map();
|
|
989
|
+
for (const a of attempts) {
|
|
990
|
+
if (a.sandbox.kind !== "docker") continue;
|
|
991
|
+
for (const s of unbrokeredSecrets(env, a.environment.layers)) out.set(s.label, s);
|
|
992
|
+
}
|
|
993
|
+
return [...out.values()];
|
|
994
|
+
}
|
|
995
|
+
/** An optional secret whose host variable isn't set never reaches the sandbox, so it gets no warning. */
|
|
996
|
+
const reachesSandbox = (s) => !s.optional || Boolean(process.env[s.from]);
|
|
997
|
+
/** `GEMINI_API_KEY → generativelanguage.googleapis.com` for each secret with `hosts`. */
|
|
998
|
+
const withHosts = (env, attempt) => [...new Set(plannedSecrets(env, attempt.environment.layers).flatMap((s) => s.hosts ? [`${s.label} → ${s.hosts.join(", ")}`] : []))];
|
|
999
|
+
/** The secrets an attempt brokers: only Docker's egress gateway swaps a placeholder for its value. */
|
|
1000
|
+
const brokeredIn = (env, attempt) => attempt.sandbox.kind === "docker" ? withHosts(env, attempt) : [];
|
|
1001
|
+
/** The plan's note for secrets with `hosts` on the fake sandbox, which only ever has their placeholder. */
|
|
1002
|
+
function unbrokeredOnFake(env, attempts) {
|
|
1003
|
+
const names = [...new Set(attempts.flatMap((a) => a.sandbox.kind === "fake" ? withHosts(env, a) : []))];
|
|
1004
|
+
return names.length ? [`not brokered (fake sandbox, placeholder only): ${names.join("; ")}`] : [];
|
|
1005
|
+
}
|
|
1006
|
+
/** The host variables an attempt's secrets read, brokered or not. */
|
|
1007
|
+
const secretVarsIn = (env, attempt) => [...new Set(plannedSecrets(env, attempt.environment.layers).map((s) => s.from))];
|
|
1008
|
+
const brokerHint = (s) => /^mcp\.[^.]+\.headers\./.test(s.label) ? "give the MCP server an https:// URL (reeval brokers its header secrets to its own host, over HTTPS only)" : `add the hosts it's sent to, e.g. secret.fromEnv('${s.from}', { hosts: ['api.example.com'] })`;
|
|
1009
|
+
const unbrokeredWarning = (s) => s.checksOnly ? `${s.label} reaches the checks (c.exec, setup steps) as its real value; the agent never gets it. To give the checks only a placeholder, ${brokerHint(s)}.` : `${s.label} is in the sandbox as its real value. To give the agent only a placeholder, ${brokerHint(s)}.`;
|
|
1010
|
+
const unbrokeredError = (s, where, configFile) => `${s.label} in ${where} isn't brokered, and secrets.unbrokered is 'error' in ${configFile}. To broker it, ${brokerHint(s)}.`;
|
|
1011
|
+
const levelOf = (where) => where.startsWith("evaluate(") ? "eval" : where.startsWith("group(") ? "group" : where.startsWith("profiles.") ? "profile" : "config";
|
|
1012
|
+
/**
|
|
1013
|
+
* A warning per privilege an attempt's Docker sandbox adds, naming the level that set the
|
|
1014
|
+
* environment which owns that sandbox: the config when no profile, group or eval did.
|
|
1015
|
+
*/
|
|
1016
|
+
function sandboxWarnings(attempts, resolved, origin) {
|
|
1017
|
+
const out = [];
|
|
1018
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1019
|
+
for (const a of attempts) {
|
|
1020
|
+
if (a.sandbox.kind !== "docker") continue;
|
|
1021
|
+
const where = sandboxSetBy(a, resolved, origin.configFile);
|
|
1022
|
+
const level = levelOf(where);
|
|
1023
|
+
const file = level === "eval" || level === "group" ? origin.file : null;
|
|
1024
|
+
if (a.sandbox.root) {
|
|
1025
|
+
const message = `running as root in the sandbox: ${where} (docker({ root: true }))`;
|
|
1026
|
+
const key = `${file}\0${message}`;
|
|
1027
|
+
if (!seen.has(key)) {
|
|
1028
|
+
seen.add(key);
|
|
1029
|
+
out.push({
|
|
1030
|
+
kind: "root",
|
|
1031
|
+
level,
|
|
1032
|
+
where,
|
|
1033
|
+
file,
|
|
1034
|
+
message
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
if (a.sandbox.capabilities?.length) {
|
|
1039
|
+
const message = `extra capabilities in the sandbox: ${where} (docker({ capabilities: [${a.sandbox.capabilities.map((c) => `'${c}'`).join(", ")}] }))`;
|
|
1040
|
+
const key = `${file}\0${message}`;
|
|
1041
|
+
if (!seen.has(key)) {
|
|
1042
|
+
seen.add(key);
|
|
1043
|
+
out.push({
|
|
1044
|
+
kind: "capabilities",
|
|
1045
|
+
level,
|
|
1046
|
+
where,
|
|
1047
|
+
file,
|
|
1048
|
+
capabilities: [...a.sandbox.capabilities],
|
|
1049
|
+
message
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
return out;
|
|
1055
|
+
}
|
|
1056
|
+
/** The plan row for an eval. */
|
|
1057
|
+
function evalPlan(r, attempts, origin) {
|
|
1058
|
+
const { resolved, skip } = r;
|
|
1059
|
+
const capture = captureNote(resolved.capture);
|
|
1060
|
+
const notes = [
|
|
1061
|
+
...r.notes,
|
|
1062
|
+
...capture ? [capture] : [],
|
|
1063
|
+
...unbrokeredOnFake(resolved.env, attempts)
|
|
1064
|
+
];
|
|
1065
|
+
return {
|
|
1066
|
+
harnesses: skip ? 0 : resolved.harness.length,
|
|
1067
|
+
variants: resolved.compare ? {
|
|
1068
|
+
run: resolved.compare.variants.length,
|
|
1069
|
+
of: resolved.compare.of
|
|
1070
|
+
} : null,
|
|
1071
|
+
...resolved.compare ? { compare: {
|
|
1072
|
+
baseline: resolved.compare.baseline.name,
|
|
1073
|
+
...resolved.compare.from ? { from: resolved.compare.from } : {}
|
|
1074
|
+
} } : {},
|
|
1075
|
+
...!skip && resolved.compare?.questions.length ? { questions: resolved.compare.questions } : {},
|
|
1076
|
+
runs: resolved.runs,
|
|
1077
|
+
...resolved.gate !== void 0 ? { gate: resolved.gate } : {},
|
|
1078
|
+
attempts: skip ? 0 : attempts.length,
|
|
1079
|
+
notes,
|
|
1080
|
+
...skip ? { skip } : {},
|
|
1081
|
+
warnings: [
|
|
1082
|
+
...environmentWarnings(attempts),
|
|
1083
|
+
...skip ? [] : questionWarnings(resolved),
|
|
1084
|
+
...unbrokered(r.resolved.env, attempts).filter(reachesSandbox).map(unbrokeredWarning)
|
|
1085
|
+
],
|
|
1086
|
+
sandboxWarnings: skip ? [] : sandboxWarnings(attempts, resolved, origin),
|
|
1087
|
+
brokered: [...new Set(attempts.flatMap((a) => brokeredIn(r.resolved.env, a)))],
|
|
1088
|
+
egress: [...new Set(attempts.map((a) => egressLabel(a.egress)).filter(Boolean))],
|
|
1089
|
+
workspaces: [...new Set(attempts.flatMap((a) => {
|
|
1090
|
+
const w = workspaceOf(a.environment.layers);
|
|
1091
|
+
return w ? [workspaceLabel(w)] : [];
|
|
1092
|
+
}))],
|
|
1093
|
+
...capture ? { capture: resolved.capture } : {}
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
/** A question that shows each side's transcript, asked of two harnesses: tool names unblind it. */
|
|
1097
|
+
function questionWarnings(resolved) {
|
|
1098
|
+
const { compare } = resolved;
|
|
1099
|
+
if (!compare || resolved.harness.length < 2) return [];
|
|
1100
|
+
return compare.questions.filter((q) => q.across !== "environment" && q.state?.transcript).map((q) => `compare question "${q.question}" shows state.transcript, which names each harness's tools, so its harness pairs aren't blind. Ask it with across: 'environment', or leave the transcript out.`);
|
|
1101
|
+
}
|
|
1102
|
+
function captureNote(c) {
|
|
1103
|
+
const parts = [];
|
|
1104
|
+
if (c.viewport === "full-page") parts.push("full-page");
|
|
1105
|
+
else if (c.viewport) parts.push(`${c.viewport.width}×${c.viewport.height}`);
|
|
1106
|
+
if (c.timeout !== void 0) parts.push(typeof c.timeout === "number" ? `${c.timeout}ms` : c.timeout);
|
|
1107
|
+
if (c.browser === false) parts.push("browser false");
|
|
1108
|
+
if (c.model) parts.push(`model ${c.model}`);
|
|
1109
|
+
return parts.length ? `capture: ${parts.join(" · ")}` : void 0;
|
|
1110
|
+
}
|
|
1111
|
+
/** `--tag a --tag !b`: every entry must hold. */
|
|
1112
|
+
function matchesTags(tags, filters) {
|
|
1113
|
+
return filters.every((f) => f.startsWith("!") ? !tags.includes(f.slice(1)) : tags.includes(f));
|
|
1114
|
+
}
|
|
1115
|
+
//#endregion
|
|
1116
|
+
export { questionOptionsSchema as A, configJudgeDataSchema as C, decideState as D, badJudgeArtifacts as E, filePartsOf as O, servedEffortOf as S, artifactRefsOf as T, unbrokeredError as _, evalPlan as a, harnessEffort as b, levelOf as c, refuseIllegalCompare as d, sandboxId as f, unbrokered as g, secretVarsIn as h, checkOptions as i, resolveJudgeArtifacts as k, matchesTags as l, sandboxSetBy as m, cascade as n, expand as o, sandboxProvider as p, checkHarnesses as r, harnessId as s, brokeredIn as t, profileJudge as u, mergeJudge as v, judgeProblem as w, resolvedEffortOf as x, resolveStateOptions as y };
|
|
1117
|
+
|
|
1118
|
+
//# sourceMappingURL=options-C0d0AzeR.js.map
|