reeval 0.10.0 → 0.12.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 +64 -39
- package/THIRD_PARTY_NOTICES.md +75 -106
- package/bin/mcp-timer.cjs +224 -0
- package/dist/{adapter-DWlpOtRn.js → adapter-DigeVFWC.js} +3 -3
- package/dist/{adapter-DWlpOtRn.js.map → adapter-DigeVFWC.js.map} +1 -1
- package/dist/{agent-CaS_2Sce.js → agent-CatUEo5h.js} +21 -6
- package/dist/{agent-CaS_2Sce.js.map → agent-CatUEo5h.js.map} +1 -1
- package/dist/{assemble-B9Alds-z.js → assemble-CF063ms6.js} +19 -17
- package/dist/assemble-CF063ms6.js.map +1 -0
- package/dist/assemble-CJoD_Rzr.js +1 -0
- package/dist/bridge/index.mjs +15 -7
- package/dist/{capture-CNb9SuU5.js → capture-BiD7lVaM.js} +26 -18
- package/dist/{capture-CNb9SuU5.js.map → capture-BiD7lVaM.js.map} +1 -1
- package/dist/capture-Dp3q7K7Y.js +1 -0
- package/dist/{cli-D303Vj5N.js → cli-B5zQCR2r.js} +266 -547
- package/dist/cli-B5zQCR2r.js.map +1 -0
- package/dist/{cli-spec-BPZbQG2p.js → cli-spec-BXIBUlaK.js} +46 -18
- package/dist/cli-spec-BXIBUlaK.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/{command-BT9a37ZR.js → command-BMHXHqMT.js} +17 -14
- package/dist/command-BMHXHqMT.js.map +1 -0
- package/dist/command-C94CQNh8.js +91 -0
- package/dist/command-C94CQNh8.js.map +1 -0
- package/dist/command-CVtgyqlM.js +335 -0
- package/dist/command-CVtgyqlM.js.map +1 -0
- package/dist/{command-BLfT7vnQ.js → command-CaZgCTIw.js} +13 -17
- package/dist/command-CaZgCTIw.js.map +1 -0
- package/dist/{command-CMJtHKRa.js → command-DTgBZR0k.js} +6 -6
- package/dist/{command-CMJtHKRa.js.map → command-DTgBZR0k.js.map} +1 -1
- package/dist/{commands-1p-bIWhN.js → commands-BXzIDB3X.js} +1 -1
- package/dist/commands-CjpGBUWr.js +666 -0
- package/dist/commands-CjpGBUWr.js.map +1 -0
- package/dist/{config-DEgnUiU-.js → config-Dv5MG7Rp.js} +31 -34
- package/dist/config-Dv5MG7Rp.js.map +1 -0
- package/dist/{context-C1mxXh80.js → context-Dgn48BqT.js} +13 -2
- package/dist/context-Dgn48BqT.js.map +1 -0
- package/dist/contrasts-CcoMLC_y.js +101 -0
- package/dist/contrasts-CcoMLC_y.js.map +1 -0
- package/dist/db-BeqRiWLl.js +602 -0
- package/dist/db-BeqRiWLl.js.map +1 -0
- package/dist/db-CJDRVQal.js +2 -0
- package/dist/{dist-B8hJImYt.js → dist-CMjp3qqF.js} +81 -14
- package/dist/dist-CMjp3qqF.js.map +1 -0
- package/dist/{docker-sZrNrr82.js → docker-BbTlMmYi.js} +2 -2
- package/dist/{docker-sZrNrr82.js.map → docker-BbTlMmYi.js.map} +1 -1
- package/dist/{docker-7b_2SaH7.js → docker-CCXdbREk.js} +20 -4
- package/dist/{docker-7b_2SaH7.js.map → docker-CCXdbREk.js.map} +1 -1
- package/dist/{docker-yauK-9X8.js → docker-DC69dKxB.js} +1 -1
- package/dist/{doctor-BYNok4UO.js → doctor-CqttrZcs.js} +56 -22
- package/dist/doctor-CqttrZcs.js.map +1 -0
- package/dist/{egress-C4PZTSSe.js → egress-BfK_O9ce.js} +435 -31
- package/dist/egress-BfK_O9ce.js.map +1 -0
- package/dist/{environment-DNjRk0nU.js → environment-BBz1Yes9.js} +225 -12
- package/dist/environment-BBz1Yes9.js.map +1 -0
- package/dist/{evaluate-D4zeRGBF.js → evaluate-CXQ1o-pd.js} +539 -562
- package/dist/evaluate-CXQ1o-pd.js.map +1 -0
- package/dist/{evaluate-C1UVL21l.d.ts → evaluate-yjZKUexW.d.ts} +2 -2
- package/dist/facts-DtENlf9s.js +898 -0
- package/dist/facts-DtENlf9s.js.map +1 -0
- package/dist/{files-BXUdY8tr.js → files-B-eUpCZ1.js} +30 -7
- package/dist/files-B-eUpCZ1.js.map +1 -0
- package/dist/files-BsQbYz2i.js +65 -0
- package/dist/files-BsQbYz2i.js.map +1 -0
- package/dist/{files-DQmHE7pb.js → files-COHQLEHE.js} +6 -17
- package/dist/files-COHQLEHE.js.map +1 -0
- package/dist/files-DqCBW7i8.js +1 -0
- package/dist/files-Ha-jsQXf.js +1 -0
- package/dist/generate-Cs2TMH_k.js +1647 -0
- package/dist/generate-Cs2TMH_k.js.map +1 -0
- package/dist/{git-DfG-YJ1u.js → git-DhykwgKF.js} +2 -2
- package/dist/{git-DfG-YJ1u.js.map → git-DhykwgKF.js.map} +1 -1
- package/dist/{harness-B8QC9u0O.js → harness-BAjw1fcr.js} +3 -3
- package/dist/{harness-B8QC9u0O.js.map → harness-BAjw1fcr.js.map} +1 -1
- package/dist/harness.d.ts +1 -1
- package/dist/harness.js +1 -1
- package/dist/{header-hK-Z_Po9.js → header-kSmWatcO.js} +10 -3
- package/dist/{header-hK-Z_Po9.js.map → header-kSmWatcO.js.map} +1 -1
- package/dist/{images-0On0cztu.js → images-BmJRuTpm.js} +61 -13
- package/dist/images-BmJRuTpm.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -4
- package/dist/init-Bwc9byQr.js +543 -0
- package/dist/init-Bwc9byQr.js.map +1 -0
- package/dist/init-D7Y1ny1z.js +1 -0
- package/dist/insights-B2Xmhz9o.js +64 -0
- package/dist/insights-B2Xmhz9o.js.map +1 -0
- package/dist/{invocations-DbGfBAA_.js → invocations-CQTbP092.js} +3 -3
- package/dist/{invocations-DbGfBAA_.js.map → invocations-CQTbP092.js.map} +1 -1
- package/dist/{judge-DW_GDOmB.js → judge-Dt6KW6_O.js} +5 -3
- package/dist/judge-Dt6KW6_O.js.map +1 -0
- package/dist/judge.d.ts +7 -6
- package/dist/judge.js +8 -7
- package/dist/judge.js.map +1 -1
- package/dist/{live-jlfszAbb.js → live-DYd2QEyR.js} +2 -2
- package/dist/{live-jlfszAbb.js.map → live-DYd2QEyR.js.map} +1 -1
- package/dist/{model-h_cDtcRr.js → model-B-mel-Gv.js} +46 -20
- package/dist/model-B-mel-Gv.js.map +1 -0
- package/dist/model-BGKYVpoj.js +1 -0
- package/dist/{model-DaVCY3rb.js → model-C7tSwT7f.js} +2 -2
- package/dist/{model-DaVCY3rb.js.map → model-C7tSwT7f.js.map} +1 -1
- package/dist/{model-oqcDplJG.js → model-CWlAqmbU.js} +150 -46
- package/dist/model-CWlAqmbU.js.map +1 -0
- package/dist/model-DHY-dfoO.js +1 -0
- package/dist/{node-B3ydHo6t.js → node-D0mtjRAO.js} +3 -3
- package/dist/{node-B3ydHo6t.js.map → node-D0mtjRAO.js.map} +1 -1
- package/dist/{options-Bh2W63dK.js → options-CPUW97_7.js} +9 -12
- package/dist/options-CPUW97_7.js.map +1 -0
- package/dist/{pairs-CnInRu-G.js → pairs-DP6XOJby.js} +2 -2
- package/dist/{pairs-CnInRu-G.js.map → pairs-DP6XOJby.js.map} +1 -1
- package/dist/{patch-Zac5aIRM.js → patch-D4oluWzH.js} +2 -2
- package/dist/{patch-Zac5aIRM.js.map → patch-D4oluWzH.js.map} +1 -1
- package/dist/{plan-C9Cy27Ay.js → plan-OkwUcJQ9.js} +31 -12
- package/dist/plan-OkwUcJQ9.js.map +1 -0
- package/dist/plugins-CUWMnSNb.js +654 -0
- package/dist/plugins-CUWMnSNb.js.map +1 -0
- package/dist/{preflight-CPWpQeBI.js → preflight-B6pY89bF.js} +1 -1
- package/dist/{preflight-D7iFPKeK.js → preflight-Bjm7_ylE.js} +14 -10
- package/dist/preflight-Bjm7_ylE.js.map +1 -0
- package/dist/{prepare-doGxaAGR.js → prepare-CQmK3SBn.js} +71 -24
- package/dist/prepare-CQmK3SBn.js.map +1 -0
- package/dist/prepare-DbdgtZKe.js +2 -0
- package/dist/{provenance-DmE9uPVt.js → provenance-B3Y17oCC.js} +2 -2
- package/dist/{provenance-DmE9uPVt.js.map → provenance-B3Y17oCC.js.map} +1 -1
- package/dist/providers-CXN2RFz-.js +1 -0
- package/dist/{providers-tAOD1NQN.js → providers-eoniqK6w.js} +24 -10
- package/dist/providers-eoniqK6w.js.map +1 -0
- package/dist/query-CLRZmWBT.js +139 -0
- package/dist/query-CLRZmWBT.js.map +1 -0
- package/dist/query-CmBDYt0_.js +1 -0
- package/dist/{questions-DjAwMxZ3.js → questions-1XOulevV.js} +6 -6
- package/dist/{questions-DjAwMxZ3.js.map → questions-1XOulevV.js.map} +1 -1
- package/dist/{record-BKmMvZ6O.js → record-kk9M1hj-.js} +8 -4
- package/dist/record-kk9M1hj-.js.map +1 -0
- package/dist/remarks-1521zwfW.js +38 -0
- package/dist/remarks-1521zwfW.js.map +1 -0
- package/dist/remarks-BhAV1H4V.js +1 -0
- package/dist/remarks-DlJCqWRs.js +278 -0
- package/dist/remarks-DlJCqWRs.js.map +1 -0
- package/dist/{remote-HDRt16ew.js → remote-cE2VWfkJ.js} +3 -3
- package/dist/{remote-HDRt16ew.js.map → remote-cE2VWfkJ.js.map} +1 -1
- package/dist/resolve-C4zeccCH.js +724 -0
- package/dist/resolve-C4zeccCH.js.map +1 -0
- package/dist/resolve-D30yZYmB.js +1 -0
- package/dist/{results-DuauQI3Z.js → results-Drtt0uFl.js} +33 -7
- package/dist/results-Drtt0uFl.js.map +1 -0
- package/dist/{resume-BRfa8E_s.js → resume-BEFazUfy.js} +4 -4
- package/dist/{resume-BRfa8E_s.js.map → resume-BEFazUfy.js.map} +1 -1
- package/dist/rules-BFzQqPIm.js +317 -0
- package/dist/rules-BFzQqPIm.js.map +1 -0
- package/dist/run-BeMy92Us.js +88 -0
- package/dist/run-BeMy92Us.js.map +1 -0
- package/dist/{sandbox-D3A8oUb7.js → sandbox-CQaJj7n9.js} +2 -2
- package/dist/{sandbox-D3A8oUb7.js.map → sandbox-CQaJj7n9.js.map} +1 -1
- package/dist/sandbox.d.ts +1 -1
- package/dist/sandbox.js +1 -1
- package/dist/server-V4-2w0l8.js +1 -0
- package/dist/{server-XDleKaF_.js → server-xK0les4f.js} +8 -8
- package/dist/{server-XDleKaF_.js.map → server-xK0les4f.js.map} +1 -1
- package/dist/{shared-DYpIb1BI.js → shared-CA61BuPJ.js} +25 -3
- package/dist/shared-CA61BuPJ.js.map +1 -0
- package/dist/source-BDow_oW8.js +1 -0
- package/dist/{source-C8xumIWj.js → source-Q1hEKbCs.js} +2 -2
- package/dist/{source-C8xumIWj.js.map → source-Q1hEKbCs.js.map} +1 -1
- package/dist/src-B9L9yGKI.js +1 -0
- package/dist/{src-ofUaYbuY.js → src-Bcah6ZkT.js} +1 -1
- package/dist/{src-DvCg0NBs.js → src-BhmaorOU.js} +28 -6
- package/dist/{src-DvCg0NBs.js.map → src-BhmaorOU.js.map} +1 -1
- package/dist/{src-CF17Ug08.js → src-C0CeKiLU.js} +19 -4
- package/dist/{src-CF17Ug08.js.map → src-C0CeKiLU.js.map} +1 -1
- package/dist/src-C0xowZ3C.js +2 -0
- package/dist/src-CHIfIKUu.js +2 -0
- package/dist/src-DLtyz4w5.js +2 -0
- package/dist/src-DPIjmReZ.js +2 -0
- package/dist/{src-C_lPtowT.js → src-DXNh5mTr.js} +1 -1
- package/dist/{src-FGXhkEkH.js → src-DiLMkIr9.js} +142 -13
- package/dist/src-DiLMkIr9.js.map +1 -0
- package/dist/{src-B7treWa8.js → src-LekQrr_S.js} +324 -32
- package/dist/src-LekQrr_S.js.map +1 -0
- package/dist/src-bCnsA6Vp.js +2 -0
- package/dist/{src-u8_ixn_F.js → src-cbCb-MPy.js} +20 -4
- package/dist/src-cbCb-MPy.js.map +1 -0
- package/dist/{src-DvK0EIZx.js → src-qGoFkuJF.js} +55 -6
- package/dist/{src-DvK0EIZx.js.map → src-qGoFkuJF.js.map} +1 -1
- package/dist/{src-jzagbuqH.js → src-ynBVosfX.js} +1 -1
- package/dist/state-BQR_BCm4.js +1 -0
- package/dist/{state-C__9swoG.js → state-BmZ8QVZ8.js} +12 -12
- package/dist/{state-C__9swoG.js.map → state-BmZ8QVZ8.js.map} +1 -1
- package/dist/studio-app/assets/index-CnYJijyI.js +58 -0
- package/dist/studio-app/index.html +1 -1
- package/dist/suite-BaHPmAGy.js +1 -0
- package/dist/suite-C8sw2pox.js +481 -0
- package/dist/suite-C8sw2pox.js.map +1 -0
- package/dist/summary-0oQNGskv.js +837 -0
- package/dist/summary-0oQNGskv.js.map +1 -0
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +2 -2
- package/dist/{text-gEcKjAyp.js → text-CrVy5ioL.js} +18 -2
- package/dist/text-CrVy5ioL.js.map +1 -0
- package/dist/{transcript-BNNANU8A.js → transcript-BTxJpW4R.js} +2 -2
- package/dist/{transcript-BNNANU8A.js.map → transcript-BTxJpW4R.js.map} +1 -1
- package/dist/types-C5vI3g1C.js.map +1 -1
- package/dist/{types-g17bRAzt.d.ts → types-GraZtfHN.d.ts} +858 -39
- package/dist/usage-BDo2q_yu.js +1 -0
- package/dist/{command-Bge8F7Ko.js → validate-BdoXUuL_.js} +35 -383
- package/dist/validate-BdoXUuL_.js.map +1 -0
- package/dist/view/index.d.ts +20 -9
- package/dist/view/index.js +7 -6
- package/dist/views-BoUoX4_A.js +125 -0
- package/dist/views-BoUoX4_A.js.map +1 -0
- package/dist/views-noVQNCUP.js +2 -0
- package/dist/{worker-entry-yOchGYYv.js → worker-entry-D0-GErmU.js} +2 -2
- package/dist/{worker-entry-yOchGYYv.js.map → worker-entry-D0-GErmU.js.map} +1 -1
- package/dist/{workspace-D8sSgpiy.js → workspace-B90YDU_a.js} +1 -1
- package/dist/{workspace-CgyHEM0l.js → workspace-tjZFkjxC.js} +4 -4
- package/dist/{workspace-CgyHEM0l.js.map → workspace-tjZFkjxC.js.map} +1 -1
- package/package.json +10 -8
- package/skills/eval-rubric/SKILL.md +130 -0
- package/dist/agent-CURAwx_q.js +0 -334
- package/dist/agent-CURAwx_q.js.map +0 -1
- package/dist/assemble-B9Alds-z.js.map +0 -1
- package/dist/assemble-J5MukZFY.js +0 -1
- package/dist/capture-jquXZBKK.js +0 -1
- package/dist/cli-D303Vj5N.js.map +0 -1
- package/dist/cli-spec-BPZbQG2p.js.map +0 -1
- package/dist/command-BLfT7vnQ.js.map +0 -1
- package/dist/command-BT9a37ZR.js.map +0 -1
- package/dist/command-Bge8F7Ko.js.map +0 -1
- package/dist/command-BkFjijN2.js +0 -1
- package/dist/commands-BFx16WB8.js +0 -225
- package/dist/commands-BFx16WB8.js.map +0 -1
- package/dist/config-DEgnUiU-.js.map +0 -1
- package/dist/context-C1mxXh80.js.map +0 -1
- package/dist/db-CdHizc4_.js +0 -1406
- package/dist/db-CdHizc4_.js.map +0 -1
- package/dist/db-Dm1cRJJP.js +0 -2
- package/dist/dist-B8hJImYt.js.map +0 -1
- package/dist/doctor-BYNok4UO.js.map +0 -1
- package/dist/duration-CEOQww97.js +0 -26
- package/dist/duration-CEOQww97.js.map +0 -1
- package/dist/egress-C4PZTSSe.js.map +0 -1
- package/dist/environment-DNjRk0nU.js.map +0 -1
- package/dist/evaluate-D4zeRGBF.js.map +0 -1
- package/dist/family-D2b7WD10.js +0 -67
- package/dist/family-D2b7WD10.js.map +0 -1
- package/dist/files-BXUdY8tr.js.map +0 -1
- package/dist/files-BhyltlDB.js +0 -1
- package/dist/files-DQmHE7pb.js.map +0 -1
- package/dist/files-ZZPqfAo6.js +0 -1
- package/dist/images-0On0cztu.js.map +0 -1
- package/dist/init-B77Rpcqc.js +0 -360
- package/dist/init-B77Rpcqc.js.map +0 -1
- package/dist/init-BrOWin3W.js +0 -1
- package/dist/judge-DW_GDOmB.js.map +0 -1
- package/dist/model-NM3ID0uR.js +0 -1
- package/dist/model-h_cDtcRr.js.map +0 -1
- package/dist/model-nwWJvaUR.js +0 -1
- package/dist/model-oqcDplJG.js.map +0 -1
- package/dist/options-Bh2W63dK.js.map +0 -1
- package/dist/plan-C9Cy27Ay.js.map +0 -1
- package/dist/preflight-D7iFPKeK.js.map +0 -1
- package/dist/prepare-BGjePFQl.js +0 -2
- package/dist/prepare-doGxaAGR.js.map +0 -1
- package/dist/process-CSVO_jsc.js +0 -119
- package/dist/process-CSVO_jsc.js.map +0 -1
- package/dist/providers-gDMko8fl.js +0 -1
- package/dist/providers-tAOD1NQN.js.map +0 -1
- package/dist/query-Bes9Kh6Y.js +0 -91
- package/dist/query-Bes9Kh6Y.js.map +0 -1
- package/dist/query-C_G_svLU.js +0 -1
- package/dist/record-BKmMvZ6O.js.map +0 -1
- package/dist/results-DuauQI3Z.js.map +0 -1
- package/dist/server-C7sg1ndo.js +0 -1
- package/dist/shared-DYpIb1BI.js.map +0 -1
- package/dist/source-fGd0SaKP.js +0 -1
- package/dist/src-B7treWa8.js.map +0 -1
- package/dist/src-DNnG4e4R.js +0 -2
- package/dist/src-DTbrY1hi.js +0 -2
- package/dist/src-DdyJEh2v.js +0 -2
- package/dist/src-Dj9PAroc.js +0 -2
- package/dist/src-Du8H85Qq.js +0 -2
- package/dist/src-FGXhkEkH.js.map +0 -1
- package/dist/src-pJZOrYR9.js +0 -1
- package/dist/src-u8_ixn_F.js.map +0 -1
- package/dist/state-CgjQo29K.js +0 -1
- package/dist/studio-app/assets/index-DrQc2Yxd.js +0 -58
- package/dist/summary-BLAVLySU.js +0 -385
- package/dist/summary-BLAVLySU.js.map +0 -1
- package/dist/summary-DgyjUh_l.js +0 -192
- package/dist/summary-DgyjUh_l.js.map +0 -1
- package/dist/text-gEcKjAyp.js.map +0 -1
- package/dist/tools-GCyNYSc1.js +0 -249
- package/dist/tools-GCyNYSc1.js.map +0 -1
- package/dist/views-BW6oCDUQ.js +0 -2
- package/dist/views-BmQF4UCI.js +0 -225
- package/dist/views-BmQF4UCI.js.map +0 -1
|
@@ -1,35 +1,34 @@
|
|
|
1
1
|
import { t as isObjectLike } from "./object-CLf2-tNq.js";
|
|
2
2
|
import { n as UsageError, r as isTimeout, t as TimeoutError } from "./errors-B6BSMs03.js";
|
|
3
|
-
import {
|
|
4
|
-
import { n as
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { c as
|
|
8
|
-
import
|
|
9
|
-
import { n as badCharacterAt } from "./config-DEgnUiU-.js";
|
|
3
|
+
import { n as dataProblem, p as parseDuration } from "./shared-CA61BuPJ.js";
|
|
4
|
+
import { D as decideState, E as badJudgeArtifacts, O as filePartsOf, T as artifactRefsOf, _ as unbrokeredError, a as evalPlan, d as refuseIllegalCompare, f as sandboxId, g as unbrokered, h as secretVarsIn, i as checkOptions, k as resolveJudgeArtifacts, l as matchesTags, m as sandboxSetBy, n as cascade, o as expand, p as sandboxProvider, s as harnessId, t as brokeredIn, v as mergeJudge, w as judgeProblem, y as resolveStateOptions } from "./options-CPUW97_7.js";
|
|
5
|
+
import { E as sandboxPaths, a as callerDir, d as environmentFrom, f as environmentPaths, h as globFilter, j as workspaceOf, m as filesOf } from "./environment-BBz1Yes9.js";
|
|
6
|
+
import { c as relativeFile, i as evalSlug, n as attemptKey, r as evalId, s as recordFileName, t as attemptId } from "./ids-Bp6ut7ZD.js";
|
|
7
|
+
import { c as judgeModelName, d as isApiError, u as runJudge } from "./judge-Dt6KW6_O.js";
|
|
8
|
+
import "./config-Dv5MG7Rp.js";
|
|
10
9
|
import { t as inCI } from "./env-BQqj93gu.js";
|
|
11
|
-
import {
|
|
10
|
+
import { t as MCP_TIMER, ut as shellQuote } from "./src-LekQrr_S.js";
|
|
11
|
+
import { o as redactSecrets } from "./redact-DJ9JCShu.js";
|
|
12
|
+
import { O as pluginSourceLabel, S as pluginBuildId, b as localSkills, c as PluginProblem, d as builtPlugins, f as droppedPaths, h as layeredPlugins, l as buildRefusal, m as fetchesAtStart, n as PLUGIN_DIR, o as PLUGIN_ROOT, p as duplicateLocalPlugin, r as PLUGIN_MANIFEST, s as PLUGIN_ROOT_VAR, v as localPlugin, w as pluginCall } from "./plugins-CUWMnSNb.js";
|
|
12
13
|
import { t as InfraError } from "./types-C5vI3g1C.js";
|
|
13
|
-
import { _ as
|
|
14
|
-
import { S as placeholder } from "./src-B7treWa8.js";
|
|
15
|
-
import { n as shellQuote, t as run } from "./process-CSVO_jsc.js";
|
|
14
|
+
import { S as writePluginFiles, _ as expectedBlockedFor, a as unenforceable, b as refusedMcp, f as agentMcp, h as claudeMcpServers, m as brokeringFor, n as allowedHosts, o as createFakeSandbox, r as egressLabel, x as refusedPlugins, y as layoutOf } from "./egress-BfK_O9ce.js";
|
|
16
15
|
import { i as registerTest, n as getRunContext, o as SUBJECT, r as registerGroup, t as collectingFile } from "./runtime-PX_oXiCS.js";
|
|
17
16
|
import { a as withDebug, i as debugTrace, n as attemptDebug, r as debugLog, t as AttemptDebug } from "./attempt-CGPV3BZY.js";
|
|
18
17
|
import { t as attemptDebugDir } from "./files-Ca0d6Uq3.js";
|
|
19
|
-
import {
|
|
20
|
-
import { a as writeCaptureSidecars, c as sandboxFor, d as workspaceFiles, l as diffSinceSnapshot, o as asCheck, r as serveCommand, s as createChecks, t as capturePage, u as snapshot } from "./capture-
|
|
21
|
-
import { t as contextTaker } from "./context-
|
|
18
|
+
import { i as formatDuration, n as clip, t as capMiddle } from "./text-CrVy5ioL.js";
|
|
19
|
+
import { a as writeCaptureSidecars, c as sandboxFor, d as workspaceFiles, l as diffSinceSnapshot, o as asCheck, r as serveCommand, s as createChecks, t as capturePage, u as snapshot } from "./capture-BiD7lVaM.js";
|
|
20
|
+
import { t as contextTaker } from "./context-Dgn48BqT.js";
|
|
21
|
+
import { a as skillFiles, i as resolveEnvironment } from "./resolve-C4zeccCH.js";
|
|
22
22
|
import { t as PrepareError } from "./provider-CmAYTQQs.js";
|
|
23
|
-
import { i as preparedWorkspace } from "./workspace-
|
|
24
|
-
import { n as judgeDiff } from "./patch-
|
|
25
|
-
import { t as transcriptForJudge } from "./transcript-
|
|
23
|
+
import { i as preparedWorkspace } from "./workspace-tjZFkjxC.js";
|
|
24
|
+
import { n as judgeDiff } from "./patch-D4oluWzH.js";
|
|
25
|
+
import { t as transcriptForJudge } from "./transcript-BTxJpW4R.js";
|
|
26
26
|
import { r as withLease } from "./client-fcwgwOAD.js";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import { parse } from "yaml";
|
|
27
|
+
import { dirname, join } from "node:path";
|
|
28
|
+
import { parse, stringify } from "yaml";
|
|
30
29
|
import { z } from "zod";
|
|
31
30
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
32
|
-
import { mkdir, readFile, rename,
|
|
31
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
33
32
|
//#region src/attempt/assertions.ts
|
|
34
33
|
const MESSAGE_LIMIT = 1024;
|
|
35
34
|
const LABEL_LIMIT = 60;
|
|
@@ -136,6 +135,11 @@ function traceEnvironment(debug, spec, env) {
|
|
|
136
135
|
setup: env.setup,
|
|
137
136
|
skills: env.skills.map((s) => s.name),
|
|
138
137
|
workspaceSkills: env.workspaceSkills.map((s) => s.name),
|
|
138
|
+
plugins: env.plugins.map((p) => ({
|
|
139
|
+
name: p.name,
|
|
140
|
+
parts: p.parts,
|
|
141
|
+
files: p.files.length
|
|
142
|
+
})),
|
|
139
143
|
mcp: Object.fromEntries(Object.entries(env.mcp).map(([name, m]) => [name, m.transport === "stdio" ? {
|
|
140
144
|
transport: m.transport,
|
|
141
145
|
command: m.command,
|
|
@@ -155,545 +159,36 @@ function traceEnvironment(debug, spec, env) {
|
|
|
155
159
|
}, "resolved environment");
|
|
156
160
|
}
|
|
157
161
|
//#endregion
|
|
158
|
-
//#region src/environment/resolve.ts
|
|
159
|
-
const SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
160
|
-
const DEFAULT_SETUP_TIMEOUT = "5m";
|
|
161
|
-
function broker(secrets, name, value, hosts, where = ["header"], phase = "agent") {
|
|
162
|
-
const at = badCharacterAt(value);
|
|
163
|
-
if (at && where.includes("header")) throw new InfraError(`${name} has whitespace or a line break in it (character ${at}), so it can't go in a request header. A key copied from a wrapped terminal line picks these up; set it again as one line.`);
|
|
164
|
-
const sub = {
|
|
165
|
-
name,
|
|
166
|
-
placeholder: placeholder(),
|
|
167
|
-
value,
|
|
168
|
-
hosts: [...hosts],
|
|
169
|
-
in: [...where],
|
|
170
|
-
phase
|
|
171
|
-
};
|
|
172
|
-
secrets.brokered.push(sub);
|
|
173
|
-
return sub.placeholder;
|
|
174
|
-
}
|
|
175
|
-
/** The host value, or its placeholder when it's brokered to `secret.hosts` (else `hosts`). */
|
|
176
|
-
function hostValue(name, secret, where, secrets, hosts = secret.hosts) {
|
|
177
|
-
const from = secret.from ?? name;
|
|
178
|
-
const value = process.env[from];
|
|
179
|
-
if (!value) {
|
|
180
|
-
if (secret.optional) {
|
|
181
|
-
secrets.unset.push(name);
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
const source = from === name ? name : `${name} (from ${from})`;
|
|
185
|
-
throw new InfraError(`Secret ${source} for ${where} isn't set. Set it in the environment or .env.local.`);
|
|
186
|
-
}
|
|
187
|
-
registerSecret(value);
|
|
188
|
-
if (secret.to === "checks") secrets.checks.push(name);
|
|
189
|
-
else secrets.names.push(name);
|
|
190
|
-
if (!hosts) return value;
|
|
191
|
-
return broker(secrets, name, value, hosts, secret.in, secret.to === "checks" ? "checks" : "agent");
|
|
192
|
-
}
|
|
193
|
-
/** Each host variable in the template, or with `hosts` one placeholder per variable. */
|
|
194
|
-
function template(value, where, secrets, hosts) {
|
|
195
|
-
const placeholders = /* @__PURE__ */ new Map();
|
|
196
|
-
return value.template.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, name) => {
|
|
197
|
-
const v = process.env[name];
|
|
198
|
-
if (!v) throw new InfraError(`secret.template for ${where} needs ${name}, which isn't set. Set it in the environment or .env.local.`);
|
|
199
|
-
registerSecret(v);
|
|
200
|
-
if (!hosts) return v;
|
|
201
|
-
const p = placeholders.get(name) ?? broker(secrets, name, v, hosts);
|
|
202
|
-
placeholders.set(name, p);
|
|
203
|
-
return p;
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
/** A `SKILL.md` split into its YAML frontmatter's `name` and `description` and its body. */
|
|
207
|
-
function parseSkillMd(text, where) {
|
|
208
|
-
const match = /^---\r?\n(?:([\s\S]*?)\r?\n)?---[ \t]*(?:\r?\n|$)([\s\S]*)$/.exec(text);
|
|
209
|
-
if (!match) return { body: text };
|
|
210
|
-
let front;
|
|
211
|
-
try {
|
|
212
|
-
front = parse(match[1] ?? "");
|
|
213
|
-
} catch (error) {
|
|
214
|
-
const reason = error.message.split("\n")[0];
|
|
215
|
-
throw new InfraError(`${where}: the SKILL.md frontmatter isn't valid YAML (${reason}). Quote values that contain ": ".`);
|
|
216
|
-
}
|
|
217
|
-
if (front != null && (typeof front !== "object" || Array.isArray(front))) throw new InfraError(`${where}: the SKILL.md frontmatter must be a YAML mapping, e.g. name: my-skill and description: … on their own lines`);
|
|
218
|
-
const field = (key) => {
|
|
219
|
-
const value = front?.[key];
|
|
220
|
-
if (value == null) return void 0;
|
|
221
|
-
if (typeof value !== "string") throw new InfraError(`${where}: the SKILL.md ${key} must be a string (got ${typeof value})`);
|
|
222
|
-
return value.trim() || void 0;
|
|
223
|
-
};
|
|
224
|
-
return {
|
|
225
|
-
name: field("name"),
|
|
226
|
-
description: field("description"),
|
|
227
|
-
body: match[2] ?? ""
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
/** A gitignore-style test for `DEFAULT_SKILL_EXCLUDE`-shaped patterns against a skill-relative path. */
|
|
231
|
-
function excludeMatcher(patterns) {
|
|
232
|
-
const rules = patterns.map((pattern) => {
|
|
233
|
-
const dirOnly = pattern.endsWith("/");
|
|
234
|
-
const body = pattern.replace(/\/+$/, "");
|
|
235
|
-
const anchored = body.includes("/");
|
|
236
|
-
const glob = body.replace(/^\/+/, "").split("*").map((part) => part.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*");
|
|
237
|
-
return {
|
|
238
|
-
dirOnly,
|
|
239
|
-
re: new RegExp(`${anchored ? "^" : "(?:^|/)"}${glob}$`)
|
|
240
|
-
};
|
|
241
|
-
});
|
|
242
|
-
return (path, isDir) => rules.some((r) => (isDir || !r.dirOnly) && r.re.test(path));
|
|
243
|
-
}
|
|
244
|
-
function parseSkill(dir, source, exclude) {
|
|
245
|
-
const front = parseSkillMd(readFileSync(join(dir, "SKILL.md"), "utf8"), `Skill ${basename(dir)} in ${source}`);
|
|
246
|
-
const name = front.name || basename(dir);
|
|
247
|
-
checkSkill(name, front.description, source);
|
|
248
|
-
const skip = excludeMatcher(exclude);
|
|
249
|
-
const paths = [];
|
|
250
|
-
const excluded = [];
|
|
251
|
-
const walk = (rel) => {
|
|
252
|
-
for (const entry of readdirSync(join(dir, rel), { withFileTypes: true })) {
|
|
253
|
-
const path = rel ? `${rel}/${entry.name}` : entry.name;
|
|
254
|
-
const isDir = entry.isDirectory();
|
|
255
|
-
if (entry.name === ".git") continue;
|
|
256
|
-
if (skip(path, isDir)) excluded.push(isDir ? `${path}/` : path);
|
|
257
|
-
else if (isDir) walk(path);
|
|
258
|
-
else if (entry.isFile() && path !== "SKILL.md") paths.push(path);
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
|
-
walk("");
|
|
262
|
-
const files = paths.sort().map((path) => ({
|
|
263
|
-
path,
|
|
264
|
-
content: readFileSync(join(dir, path), "utf8")
|
|
265
|
-
}));
|
|
266
|
-
return {
|
|
267
|
-
skill: {
|
|
268
|
-
name,
|
|
269
|
-
description: front.description ?? "",
|
|
270
|
-
content: front.body,
|
|
271
|
-
files
|
|
272
|
-
},
|
|
273
|
-
excluded: excluded.sort()
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
function checkSkill(name, description, source) {
|
|
277
|
-
if (!SKILL_NAME.test(name)) throw new InfraError(`Skill "${name}" in ${source}: the name must be kebab-case, e.g. my-skill (lower-case letters, digits and -)`);
|
|
278
|
-
if (!description) throw new InfraError(`Skill "${name}" in ${source} has no description in its SKILL.md frontmatter. Add a description: line saying when to use it.`);
|
|
279
|
-
}
|
|
280
|
-
function loadSkills(dir, source, only, exclude = DEFAULT_SKILL_EXCLUDE) {
|
|
281
|
-
if (!existsSync(dir) || !statSync(dir).isDirectory()) throw new InfraError(`Skill source ${source} isn't a directory. Point it at a skill's directory, or at a directory of skills.`);
|
|
282
|
-
const dirs = existsSync(join(dir, "SKILL.md")) ? [dir] : readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync(join(dir, e.name, "SKILL.md"))).map((e) => join(dir, e.name)).sort();
|
|
283
|
-
if (dirs.length === 0) throw new InfraError(`Skill source ${source} has no SKILL.md (and no subdirectories with one)`);
|
|
284
|
-
const skills = dirs.map((d) => parseSkill(d, source, exclude));
|
|
285
|
-
if (!only) return skills;
|
|
286
|
-
const missing = only.filter((n) => !skills.some((s) => s.skill.name === n));
|
|
287
|
-
if (missing.length) throw new InfraError(`only: ${JSON.stringify(missing)} not found in ${source} (found: ${skills.map((s) => s.skill.name).join(", ")})`);
|
|
288
|
-
return skills.filter((s) => only.includes(s.skill.name));
|
|
289
|
-
}
|
|
290
|
-
async function git(args, cwd, what) {
|
|
291
|
-
const r = await run("git", args, { cwd });
|
|
292
|
-
if (r.exitCode !== 0) throw new InfraError(`${what} failed: ${r.stderr.trim()}`);
|
|
293
|
-
return r.stdout.trim();
|
|
294
|
-
}
|
|
295
|
-
/** One clone per source and ref per worker process; the commit is recorded. */
|
|
296
|
-
const clones = /* @__PURE__ */ new Map();
|
|
297
|
-
function cloneSkills(root, url, ref) {
|
|
298
|
-
const key = `${url}#${ref ?? ""}`;
|
|
299
|
-
let clone = clones.get(key);
|
|
300
|
-
if (!clone) {
|
|
301
|
-
clone = (async () => {
|
|
302
|
-
const dir = join(root, ".reeval", "cache", "skills", `${sha256(key).slice(0, 16)}-${process.pid}`);
|
|
303
|
-
await rm(dir, {
|
|
304
|
-
recursive: true,
|
|
305
|
-
force: true
|
|
306
|
-
});
|
|
307
|
-
await mkdir(dir, { recursive: true });
|
|
308
|
-
const what = `Cloning ${url}${ref ? ` at ${ref}` : ""}`;
|
|
309
|
-
await git(["init", "-q"], dir, what);
|
|
310
|
-
await git([
|
|
311
|
-
"fetch",
|
|
312
|
-
"-q",
|
|
313
|
-
"--depth",
|
|
314
|
-
"1",
|
|
315
|
-
url,
|
|
316
|
-
ref ?? "HEAD"
|
|
317
|
-
], dir, what);
|
|
318
|
-
await git([
|
|
319
|
-
"-c",
|
|
320
|
-
"advice.detachedHead=false",
|
|
321
|
-
"checkout",
|
|
322
|
-
"-q",
|
|
323
|
-
"FETCH_HEAD"
|
|
324
|
-
], dir, what);
|
|
325
|
-
return {
|
|
326
|
-
dir,
|
|
327
|
-
commit: await git(["rev-parse", "HEAD"], dir, what)
|
|
328
|
-
};
|
|
329
|
-
})();
|
|
330
|
-
clones.set(key, clone);
|
|
331
|
-
clone.catch(() => clones.delete(key));
|
|
332
|
-
}
|
|
333
|
-
return clone;
|
|
334
|
-
}
|
|
335
|
-
const packs = /* @__PURE__ */ new Map();
|
|
336
|
-
/** `npm pack` into the cache, unpacked; returns the package directory. */
|
|
337
|
-
function unpackNpm(root, spec) {
|
|
338
|
-
let pack = packs.get(spec);
|
|
339
|
-
if (!pack) {
|
|
340
|
-
pack = (async () => {
|
|
341
|
-
const dir = join(root, ".reeval", "cache", "skills", `npm-${sha256(spec).slice(0, 16)}-${process.pid}`);
|
|
342
|
-
await rm(dir, {
|
|
343
|
-
recursive: true,
|
|
344
|
-
force: true
|
|
345
|
-
});
|
|
346
|
-
await mkdir(dir, { recursive: true });
|
|
347
|
-
const packed = await run("npm", [
|
|
348
|
-
"pack",
|
|
349
|
-
spec,
|
|
350
|
-
"--silent",
|
|
351
|
-
"--pack-destination",
|
|
352
|
-
dir
|
|
353
|
-
], { cwd: dir });
|
|
354
|
-
const tarball = packed.stdout.trim().split("\n").at(-1);
|
|
355
|
-
if (packed.exitCode !== 0 || !tarball) throw new InfraError(`npm pack ${spec} failed: ${packed.stderr.trim()}`);
|
|
356
|
-
const untar = await run("tar", ["-xzf", tarball], { cwd: dir });
|
|
357
|
-
if (untar.exitCode !== 0) throw new InfraError(`Unpacking ${spec} failed: ${untar.stderr.trim()}`);
|
|
358
|
-
return join(dir, "package");
|
|
359
|
-
})();
|
|
360
|
-
packs.set(spec, pack);
|
|
361
|
-
pack.catch(() => packs.delete(spec));
|
|
362
|
-
}
|
|
363
|
-
return pack;
|
|
364
|
-
}
|
|
365
|
-
async function resolveSkills(root, s) {
|
|
366
|
-
switch (s.source) {
|
|
367
|
-
case "local": {
|
|
368
|
-
const label = `local:${relative(root, s.path).split(sep).join("/") || "."}`;
|
|
369
|
-
return {
|
|
370
|
-
skills: loadSkills(s.path, label, s.only, s.exclude),
|
|
371
|
-
source: label,
|
|
372
|
-
commit: null
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
case "git": {
|
|
376
|
-
const { url } = parseGitRepo(s.repo);
|
|
377
|
-
const { dir, commit } = await cloneSkills(root, url, s.ref);
|
|
378
|
-
const path = s.path ?? (!existsSync(join(dir, "SKILL.md")) && existsSync(join(dir, "skills")) ? "skills" : "");
|
|
379
|
-
const label = `${s.repo}${s.ref ? `#${s.ref}` : ""}${path ? `:${path}` : ""}`;
|
|
380
|
-
return {
|
|
381
|
-
skills: loadSkills(join(dir, path), label, s.only, s.exclude),
|
|
382
|
-
source: label,
|
|
383
|
-
commit
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
case "registry": {
|
|
387
|
-
const [repo = "", name = ""] = s.id.split("@");
|
|
388
|
-
const { dir, commit } = await cloneSkills(root, `https://github.com/${repo}.git`, s.version);
|
|
389
|
-
const label = `registry:${s.id}${s.version ? `#${s.version}` : ""}`;
|
|
390
|
-
return {
|
|
391
|
-
skills: loadSkills(existsSync(join(dir, "skills")) ? join(dir, "skills") : dir, label, [name]),
|
|
392
|
-
source: label,
|
|
393
|
-
commit
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
case "npm": {
|
|
397
|
-
const dir = await unpackNpm(root, s.spec);
|
|
398
|
-
const label = `npm:${isAbsolute(s.spec) ? relative(root, s.spec).split(sep).join("/") : s.spec}:${s.path}`;
|
|
399
|
-
return {
|
|
400
|
-
skills: loadSkills(join(dir, s.path), label, s.only, s.exclude),
|
|
401
|
-
source: label,
|
|
402
|
-
commit: null
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
case "inline": {
|
|
406
|
-
checkSkill(s.name, s.description, "skill.inline");
|
|
407
|
-
const files = Object.entries(s.files).sort(([a], [b]) => a < b ? -1 : 1).map(([path, content]) => ({
|
|
408
|
-
path,
|
|
409
|
-
content
|
|
410
|
-
}));
|
|
411
|
-
return {
|
|
412
|
-
skills: [{
|
|
413
|
-
skill: {
|
|
414
|
-
name: s.name,
|
|
415
|
-
description: s.description,
|
|
416
|
-
content: s.content,
|
|
417
|
-
files
|
|
418
|
-
},
|
|
419
|
-
excluded: []
|
|
420
|
-
}],
|
|
421
|
-
source: "inline",
|
|
422
|
-
commit: null
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
const skillHash = (s) => sha256(JSON.stringify(s));
|
|
428
|
-
const readHostFile = (f, where) => {
|
|
429
|
-
if (!existsSync(f.path)) throw new InfraError(`${f.path} for ${where} doesn't exist. file() paths resolve against the file that calls it, or reeval.yaml's directory.`);
|
|
430
|
-
return readFileSync(f.path);
|
|
431
|
-
};
|
|
432
|
-
/**
|
|
433
|
-
* Layers the environment (root first; a later layer wins on shared keys, and each such key is
|
|
434
|
-
* an override), over the options `env` merged down the cascade. `where` names the attempt, and
|
|
435
|
-
* `rulesFile` is the workspace file the harness's agent reads `rules` from.
|
|
436
|
-
*/
|
|
437
|
-
async function resolveEnvironment(environment, optionsEnv, root, where, rulesFile, egress = "allow-all", prepared = () => void 0) {
|
|
438
|
-
const envWhere = `environment ${environment.name} (${where})`;
|
|
439
|
-
const overrides = /* @__PURE__ */ new Set();
|
|
440
|
-
const env = { ...optionsEnv };
|
|
441
|
-
const files = /* @__PURE__ */ new Map();
|
|
442
|
-
const mcp = /* @__PURE__ */ new Map();
|
|
443
|
-
const skills = /* @__PURE__ */ new Map();
|
|
444
|
-
let rules;
|
|
445
|
-
const setup = [];
|
|
446
|
-
const seen = {
|
|
447
|
-
env: /* @__PURE__ */ new Set(),
|
|
448
|
-
files: /* @__PURE__ */ new Set(),
|
|
449
|
-
mcp: /* @__PURE__ */ new Set()
|
|
450
|
-
};
|
|
451
|
-
let egressSet = false;
|
|
452
|
-
let workspace;
|
|
453
|
-
let sandboxSet = false;
|
|
454
|
-
for (const { spec } of environment.layers) {
|
|
455
|
-
if (spec.sandbox !== void 0) {
|
|
456
|
-
if (sandboxSet) overrides.add("sandbox");
|
|
457
|
-
sandboxSet = true;
|
|
458
|
-
}
|
|
459
|
-
if (spec.workspace !== void 0) {
|
|
460
|
-
if (workspace) overrides.add("workspace");
|
|
461
|
-
workspace = spec.workspace;
|
|
462
|
-
}
|
|
463
|
-
if (spec.egress !== void 0) {
|
|
464
|
-
if (egressSet) overrides.add("egress");
|
|
465
|
-
egressSet = true;
|
|
466
|
-
}
|
|
467
|
-
for (const [k, v] of Object.entries(spec.env ?? {})) {
|
|
468
|
-
if (seen.env.has(k)) overrides.add(`env.${k}`);
|
|
469
|
-
seen.env.add(k);
|
|
470
|
-
env[k] = v;
|
|
471
|
-
}
|
|
472
|
-
for (const [path, v] of Object.entries(spec.files ?? {})) {
|
|
473
|
-
if (seen.files.has(path)) overrides.add(`files.${path}`);
|
|
474
|
-
seen.files.add(path);
|
|
475
|
-
files.set(path, v);
|
|
476
|
-
}
|
|
477
|
-
if (spec.rules !== void 0) {
|
|
478
|
-
if (rules !== void 0) overrides.add("rules");
|
|
479
|
-
rules = spec.rules;
|
|
480
|
-
}
|
|
481
|
-
for (const [name, server] of Object.entries(spec.mcp ?? {})) {
|
|
482
|
-
if (seen.mcp.has(name)) overrides.add(`mcp.${name}`);
|
|
483
|
-
seen.mcp.add(name);
|
|
484
|
-
mcp.set(name, server);
|
|
485
|
-
}
|
|
486
|
-
for (const step of spec.setup ?? []) {
|
|
487
|
-
const s = typeof step === "string" ? { run: step } : step;
|
|
488
|
-
setup.push({
|
|
489
|
-
run: s.run,
|
|
490
|
-
timeoutMs: parseDuration(s.timeout ?? DEFAULT_SETUP_TIMEOUT),
|
|
491
|
-
...s.cwd ? { cwd: s.cwd } : {}
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
const layerSkills = /* @__PURE__ */ new Set();
|
|
495
|
-
for (const source of spec.skills ?? []) {
|
|
496
|
-
const found = await resolveSkills(root, source);
|
|
497
|
-
for (const loaded of found.skills) {
|
|
498
|
-
const name = loaded.skill.name;
|
|
499
|
-
if (skills.has(name) && !layerSkills.has(name)) overrides.add(`skills.${name}`);
|
|
500
|
-
layerSkills.add(name);
|
|
501
|
-
skills.delete(name);
|
|
502
|
-
skills.set(name, {
|
|
503
|
-
...loaded,
|
|
504
|
-
placement: source.placement,
|
|
505
|
-
source: found.source,
|
|
506
|
-
commit: found.commit
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
const secrets = {
|
|
512
|
-
names: [],
|
|
513
|
-
checks: [],
|
|
514
|
-
unset: [],
|
|
515
|
-
brokered: []
|
|
516
|
-
};
|
|
517
|
-
const vars = {};
|
|
518
|
-
const checkVars = {};
|
|
519
|
-
for (const [name, value] of Object.entries(env)) {
|
|
520
|
-
if (!isSecret(value)) {
|
|
521
|
-
vars[name] = value;
|
|
522
|
-
continue;
|
|
523
|
-
}
|
|
524
|
-
const resolved = hostValue(name, value, envWhere, secrets);
|
|
525
|
-
if (resolved === void 0) continue;
|
|
526
|
-
if (value.to === "checks") checkVars[name] = resolved;
|
|
527
|
-
else vars[name] = resolved;
|
|
528
|
-
}
|
|
529
|
-
const secretOf = (value, key, hosts) => {
|
|
530
|
-
if (typeof value === "string") return value;
|
|
531
|
-
if (isSecretTemplate(value)) return template(value, envWhere, secrets, hosts);
|
|
532
|
-
return hostValue(key, value, envWhere, secrets, value.hosts ?? hosts);
|
|
533
|
-
};
|
|
534
|
-
const resolvedMcp = {};
|
|
535
|
-
for (const [name, s] of mcp) if (s.transport === "stdio") {
|
|
536
|
-
const e = {};
|
|
537
|
-
for (const [k, v] of Object.entries(s.env)) {
|
|
538
|
-
const r = secretOf(v, k);
|
|
539
|
-
if (r !== void 0) e[k] = r;
|
|
540
|
-
}
|
|
541
|
-
resolvedMcp[name] = {
|
|
542
|
-
transport: "stdio",
|
|
543
|
-
command: s.command,
|
|
544
|
-
args: s.args,
|
|
545
|
-
env: e
|
|
546
|
-
};
|
|
547
|
-
} else {
|
|
548
|
-
const h = {};
|
|
549
|
-
const hosts = mcpHosts(s.url);
|
|
550
|
-
for (const [k, v] of Object.entries(s.headers)) {
|
|
551
|
-
const r = secretOf(v, k, hosts);
|
|
552
|
-
if (r !== void 0) h[k] = r;
|
|
553
|
-
}
|
|
554
|
-
resolvedMcp[name] = {
|
|
555
|
-
transport: s.transport,
|
|
556
|
-
url: s.url,
|
|
557
|
-
headers: h
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
const fileList = [...files].map(([path, v]) => ({
|
|
561
|
-
path,
|
|
562
|
-
content: isHostFile(v) ? readHostFile(v, envWhere) : v,
|
|
563
|
-
...isHostFile(v) && statSync(v.path).mode & 64 ? { executable: true } : {}
|
|
564
|
-
}));
|
|
565
|
-
const rulesContent = rules === void 0 ? void 0 : isHostFile(rules) ? Buffer.from(readHostFile(rules, envWhere)).toString("utf8") : rules;
|
|
566
|
-
const skillList = [...skills.values()];
|
|
567
|
-
const skillRecords = skillList.map(({ skill, placement, source, commit, excluded }) => ({
|
|
568
|
-
name: skill.name,
|
|
569
|
-
source,
|
|
570
|
-
commit,
|
|
571
|
-
sha256: skillHash(skill),
|
|
572
|
-
placement,
|
|
573
|
-
excluded
|
|
574
|
-
}));
|
|
575
|
-
const fileRecords = fileList.map((f) => ({
|
|
576
|
-
path: f.path,
|
|
577
|
-
sha256: sha256(f.content)
|
|
578
|
-
}));
|
|
579
|
-
const executable = fileList.flatMap((f) => f.executable ? [f.path] : []);
|
|
580
|
-
const rulesRecord = rulesContent === void 0 ? void 0 : {
|
|
581
|
-
path: rulesFile,
|
|
582
|
-
sha256: sha256(rulesContent)
|
|
583
|
-
};
|
|
584
|
-
if (rulesContent !== void 0) fileList.push({
|
|
585
|
-
path: rulesFile,
|
|
586
|
-
content: rulesContent
|
|
587
|
-
});
|
|
588
|
-
const secretNames = [.../* @__PURE__ */ new Set([...secrets.names, ...secrets.checks])].sort();
|
|
589
|
-
const brokered = secrets.brokered.map((b) => JSON.stringify([
|
|
590
|
-
b.name,
|
|
591
|
-
b.hosts,
|
|
592
|
-
b.in,
|
|
593
|
-
b.phase
|
|
594
|
-
])).sort();
|
|
595
|
-
const brokeredNames = [...new Set(secrets.brokered.map((b) => b.name))].sort();
|
|
596
|
-
const varNames = Object.keys(vars).filter((n) => !secrets.names.includes(n)).sort();
|
|
597
|
-
const literalVars = Object.fromEntries(varNames.map((n) => [n, vars[n]]));
|
|
598
|
-
const mcpShape = Object.fromEntries(Object.entries(resolvedMcp).map(([name, s]) => s.transport === "stdio" ? [name, {
|
|
599
|
-
command: s.command,
|
|
600
|
-
args: s.args,
|
|
601
|
-
env: Object.keys(s.env).sort()
|
|
602
|
-
}] : [name, {
|
|
603
|
-
transport: s.transport,
|
|
604
|
-
url: s.url,
|
|
605
|
-
headers: Object.keys(s.headers).sort()
|
|
606
|
-
}]));
|
|
607
|
-
const tree = workspace && prepared(workspace);
|
|
608
|
-
if (workspace && !tree) throw new InfraError(`The workspace git(${JSON.stringify(workspaceLabel(workspace))}) for ${envWhere} wasn't prepared before the run started`);
|
|
609
|
-
const workspaceRecord = workspace && tree ? {
|
|
610
|
-
source: isAbsolute(workspace.url) ? relative(root, workspace.url).split(sep).join("/") || "." : workspace.repo,
|
|
611
|
-
ref: workspace.ref ?? null,
|
|
612
|
-
path: workspace.path ?? null,
|
|
613
|
-
commit: tree.commit,
|
|
614
|
-
tree: tree.tree,
|
|
615
|
-
...workspace.init ? { init: true } : {}
|
|
616
|
-
} : void 0;
|
|
617
|
-
const digest = `sha256:${sha256(JSON.stringify({
|
|
618
|
-
setup,
|
|
619
|
-
skills: skillRecords.map((s) => [
|
|
620
|
-
s.name,
|
|
621
|
-
s.sha256,
|
|
622
|
-
s.placement
|
|
623
|
-
]),
|
|
624
|
-
mcp: mcpShape,
|
|
625
|
-
files: fileRecords,
|
|
626
|
-
...executable.length ? { executable } : {},
|
|
627
|
-
rules: rulesRecord?.sha256,
|
|
628
|
-
vars: literalVars,
|
|
629
|
-
secrets: secretNames,
|
|
630
|
-
checks: [...secrets.checks].sort(),
|
|
631
|
-
unset: [...secrets.unset].sort(),
|
|
632
|
-
brokered,
|
|
633
|
-
...egress === "allow-all" ? {} : { egress: typeof egress === "string" ? egress : [...egress.allow].sort() },
|
|
634
|
-
...workspaceRecord ? { workspace: workspaceRecord.init ? [workspaceRecord.tree, "init"] : workspaceRecord.tree } : {}
|
|
635
|
-
}))}`;
|
|
636
|
-
return {
|
|
637
|
-
record: {
|
|
638
|
-
name: environment.name,
|
|
639
|
-
parent: environment.parent,
|
|
640
|
-
overrides: [...overrides],
|
|
641
|
-
digest,
|
|
642
|
-
setup: setup.map(stepCommand),
|
|
643
|
-
skills: skillRecords,
|
|
644
|
-
mcp: Object.keys(resolvedMcp).sort(),
|
|
645
|
-
files: rulesRecord ? [...fileRecords, rulesRecord] : fileRecords,
|
|
646
|
-
vars: varNames,
|
|
647
|
-
secrets: secretNames,
|
|
648
|
-
brokered: brokeredNames,
|
|
649
|
-
unset: [...secrets.unset].sort(),
|
|
650
|
-
egress,
|
|
651
|
-
...workspaceRecord ? { workspace: workspaceRecord } : {}
|
|
652
|
-
},
|
|
653
|
-
setup,
|
|
654
|
-
skills: skillList.filter((s) => s.placement === "harness").map((s) => s.skill),
|
|
655
|
-
workspaceSkills: skillList.filter((s) => s.placement === "workspace").map((s) => s.skill),
|
|
656
|
-
mcp: resolvedMcp,
|
|
657
|
-
files: fileList,
|
|
658
|
-
vars,
|
|
659
|
-
checkVars,
|
|
660
|
-
substitutions: secrets.brokered,
|
|
661
|
-
...workspace && tree ? { workspace: {
|
|
662
|
-
tar: tree.tar,
|
|
663
|
-
label: workspaceLabel(workspace),
|
|
664
|
-
commit: tree.commit,
|
|
665
|
-
...workspace.init ? { init: true } : {}
|
|
666
|
-
} } : {}
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
|
-
/** A skill as a `SKILL.md` file plus its other files, for writing into a directory. */
|
|
670
|
-
function skillFiles(skill) {
|
|
671
|
-
return [{
|
|
672
|
-
path: "SKILL.md",
|
|
673
|
-
content: `${`---\nname: ${skill.name}\ndescription: ${JSON.stringify(skill.description)}\n---\n`}${skill.content}`
|
|
674
|
-
}, ...skill.files];
|
|
675
|
-
}
|
|
676
|
-
//#endregion
|
|
677
162
|
//#region src/harness/fake.ts
|
|
678
163
|
const lineCount = (text) => text ? text.split("\n").length - (text.endsWith("\n") ? 1 : 0) : 0;
|
|
679
164
|
/**
|
|
680
165
|
* Writes canned files and returns a canned message on every turn, after running its `script`'s
|
|
681
166
|
* real sandbox actions. Harness-placed skills are written under `~/` where its layout's adapter
|
|
682
|
-
* puts them (`~/.agents/skills` by default, as Cursor's does).
|
|
167
|
+
* puts them (`~/.agents/skills` by default, as Cursor's does). Plugins are already copied under
|
|
168
|
+
* `~/.reeval/plugins/`; where its layout takes plugin items flat, their skills arrive in `skills`.
|
|
169
|
+
* Where it takes them namespaced, it writes its layout's plugin files (Claude Code's settings)
|
|
170
|
+
* and leaves the plugins' MCP servers to the plugins, as that agent does.
|
|
683
171
|
*/
|
|
684
172
|
const createFakeHarness = (descriptor) => {
|
|
685
173
|
let starts = 0;
|
|
686
|
-
return { async start(sandbox, _signal, { skills, workspaceSkills, mcp }) {
|
|
174
|
+
return { async start(sandbox, _signal, { skills, workspaceSkills, mcp: all, plugins }) {
|
|
687
175
|
if (descriptor.failWith) throw new InfraError(descriptor.failWith);
|
|
688
176
|
const hangs = descriptor.startHangs;
|
|
689
177
|
if (hangs === true || starts++ < (hangs ?? 0)) return new Promise(() => {});
|
|
690
|
-
const
|
|
178
|
+
const layout = layoutOf(descriptor);
|
|
179
|
+
const mcp = agentMcp(layout, all, plugins);
|
|
180
|
+
for (const path of await writePluginFiles(sandbox, layout, plugins)) debugLog("env", `plugin settings → ${path} by the fake agent`);
|
|
181
|
+
const dir = `~/${layout.harnessSkillDir}`;
|
|
691
182
|
for (const skill of skills) {
|
|
692
183
|
for (const f of skillFiles(skill)) await sandbox.writeFile(`${dir}/${skill.name}/${f.path}`, f.content);
|
|
693
184
|
debugLog("env", `skill ${skill.name} placed at ${dir}/${skill.name}/ by the fake agent`);
|
|
694
185
|
}
|
|
695
186
|
const names = [...skills.map((s) => s.name), ...workspaceSkills];
|
|
696
|
-
const loaded = [
|
|
187
|
+
const loaded = [
|
|
188
|
+
names.length ? `skills: ${names.join(", ")}` : "",
|
|
189
|
+
plugins.length ? `plugins: ${plugins.map((p) => `${p.name} (${p.parts.join(", ")})`).join(", ")}` : "",
|
|
190
|
+
Object.keys(mcp).length ? `mcp: ${Object.keys(mcp).join(", ")}` : ""
|
|
191
|
+
].filter(Boolean);
|
|
697
192
|
let calls = 0;
|
|
698
193
|
let turns = 0;
|
|
699
194
|
const skilled = names.length > 0 && descriptor.withSkill;
|
|
@@ -882,7 +377,7 @@ async function callMcpTool(sandbox, servers, name, tool, signal) {
|
|
|
882
377
|
" trap '' PIPE",
|
|
883
378
|
` printf '%s\\n' ${messages.join(" ")} 2>/dev/null`,
|
|
884
379
|
" i=0",
|
|
885
|
-
` while [ $i -lt
|
|
380
|
+
` while [ $i -lt 900 ] && [ ! -f "$d/code" ] && ! grep -Eq '"id": ?2[,}]' "$d/out"; do sleep 0.1; i=$((i + 1)); done`,
|
|
886
381
|
`} | { ${command} > "$d/out" 2> "$d/err"; echo $? > "$d/code"; }`,
|
|
887
382
|
"code=$(cat \"$d/code\")",
|
|
888
383
|
"cat \"$d/out\"; cat \"$d/err\" >&2; rm -rf \"$d\"; exit $code"
|
|
@@ -915,7 +410,7 @@ async function callMcpTool(sandbox, servers, name, tool, signal) {
|
|
|
915
410
|
//#region src/harness/index.ts
|
|
916
411
|
async function createHarness(descriptor) {
|
|
917
412
|
if (descriptor.kind === "fake") return createFakeHarness(descriptor);
|
|
918
|
-
const { createAgentHarness } = await import("./agent-
|
|
413
|
+
const { createAgentHarness } = await import("./agent-CatUEo5h.js");
|
|
919
414
|
return createAgentHarness(descriptor);
|
|
920
415
|
}
|
|
921
416
|
/** Where each harness's agent looks for repo skills, relative to the workspace. */
|
|
@@ -929,7 +424,10 @@ const workspaceSkillDir = (descriptor) => layoutOf(descriptor).workspaceSkillDir
|
|
|
929
424
|
*/
|
|
930
425
|
async function prepareEnvironment(a, root, runId, where, signal) {
|
|
931
426
|
const { spec, record } = a;
|
|
932
|
-
const env = await resolveEnvironment(spec.environment, spec.env, root, where, layoutOf(spec.harness).rulesFile, spec.egress.policy, (w) => preparedWorkspace(root, runId, w)
|
|
427
|
+
const env = await resolveEnvironment(spec.environment, spec.env, root, where, layoutOf(spec.harness).rulesFile, spec.egress.policy, (w) => preparedWorkspace(root, runId, w), {
|
|
428
|
+
harness: spec.harness,
|
|
429
|
+
allowed: allowedHosts(spec.egress)
|
|
430
|
+
});
|
|
933
431
|
a.env = env;
|
|
934
432
|
record.environment = env.record;
|
|
935
433
|
const { secrets, brokered } = env.record;
|
|
@@ -942,13 +440,29 @@ async function prepareEnvironment(a, root, runId, where, signal) {
|
|
|
942
440
|
if (debug) traceEnvironment(debug, spec, env);
|
|
943
441
|
record.image = null;
|
|
944
442
|
if (spec.sandbox.kind === "docker") {
|
|
945
|
-
const { preparedFor } = await import("./preflight-
|
|
946
|
-
const { attemptImage, dockerProvider } = await import("./providers-
|
|
443
|
+
const { preparedFor } = await import("./preflight-B6pY89bF.js");
|
|
444
|
+
const { attemptImage, dockerProvider, preparedImage } = await import("./providers-CXN2RFz-.js");
|
|
947
445
|
signal.throwIfAborted();
|
|
948
|
-
const
|
|
446
|
+
const provider = dockerProvider({ engine: spec.sandbox.engine ?? "docker" });
|
|
447
|
+
const builds = builtPlugins(spec.environment.layers);
|
|
448
|
+
const prepared = preparedFor(root, runId, provider, spec.sandbox, spec.harness, builds);
|
|
949
449
|
if (!prepared) throw new InfraError(`The sandbox image for ${where} wasn't prepared before the run started`);
|
|
950
450
|
record.image = attemptImage(prepared);
|
|
951
451
|
a.image = record.image.ref;
|
|
452
|
+
const built = preparedImage(prepared).plugins ?? [];
|
|
453
|
+
for (const [i, plugin] of env.plugins.entries()) {
|
|
454
|
+
if (plugin.declared.build === void 0) continue;
|
|
455
|
+
const found = built.find((b) => b.id === pluginBuildId(plugin.declared));
|
|
456
|
+
if (!found) throw new InfraError(`The ${plugin.name} plugin's build for ${where} wasn't prepared before the run started`);
|
|
457
|
+
if (found.commit !== plugin.commit) throw new InfraError(`The ${plugin.name} plugin's ref moved during the run: it was built at ${found.commit?.slice(0, 12)} and is now ${plugin.commit?.slice(0, 12)}. Pin ref to a commit or tag.`);
|
|
458
|
+
plugin.built = {
|
|
459
|
+
dir: found.dir,
|
|
460
|
+
tag: found.tag
|
|
461
|
+
};
|
|
462
|
+
const pluginRecord = env.record.plugins?.[i];
|
|
463
|
+
if (pluginRecord) pluginRecord.build = found.tag;
|
|
464
|
+
debugLog("image", `plugin ${plugin.name} built in ${found.tag}`);
|
|
465
|
+
}
|
|
952
466
|
const { ref, id, base, install, agentCli } = record.image;
|
|
953
467
|
debugLog("image", `ready ${ref} (${id.replace(/^sha256:/, "").slice(0, 12)}) · prepared before the run${ref !== base ? ` · on ${base}` : ""}${agentCli ? ` · agent CLI ${agentCli}` : ""}${install.length ? ` · install: ${install.join(" && ")}` : ""}`);
|
|
954
468
|
} else debugLog("image", `none: the ${spec.sandbox.kind} sandbox runs on the host`);
|
|
@@ -1243,7 +757,7 @@ async function createSandbox(descriptor, options) {
|
|
|
1243
757
|
switch (descriptor.kind) {
|
|
1244
758
|
case "fake": return await createFakeSandbox(descriptor, options.env);
|
|
1245
759
|
case "docker": {
|
|
1246
|
-
const { createDockerSandbox } = await import("./docker-
|
|
760
|
+
const { createDockerSandbox } = await import("./docker-DC69dKxB.js");
|
|
1247
761
|
return await createDockerSandbox(descriptor, options);
|
|
1248
762
|
}
|
|
1249
763
|
}
|
|
@@ -1322,11 +836,12 @@ async function recordOom(a, sandbox) {
|
|
|
1322
836
|
}
|
|
1323
837
|
/**
|
|
1324
838
|
* The attempt's egress policy and, when its sandbox records it, what it contacted and was refused.
|
|
1325
|
-
* Refused hosts the harness always tries
|
|
839
|
+
* Refused hosts the harness always tries (in a session like this one) are `expectedBlocked`,
|
|
840
|
+
* summarised in one `-v` line.
|
|
1326
841
|
*/
|
|
1327
842
|
function recordEgress(a, sandbox) {
|
|
1328
843
|
const traffic = sandbox?.traffic?.();
|
|
1329
|
-
const expected = expectedBlockedFor(a.spec.harness);
|
|
844
|
+
const expected = expectedBlockedFor(a.spec.harness, (a.env?.plugins.length ?? 0) > 0);
|
|
1330
845
|
const refused = traffic?.blocked ?? [];
|
|
1331
846
|
const isExpected = (h) => !!expected?.hosts.includes(h.host);
|
|
1332
847
|
const expectedBlocked = refused.filter(isExpected);
|
|
@@ -1377,6 +892,118 @@ function turnErrors(transcripts) {
|
|
|
1377
892
|
});
|
|
1378
893
|
}
|
|
1379
894
|
//#endregion
|
|
895
|
+
//#region src/harness/unpack.ts
|
|
896
|
+
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
897
|
+
const FRONTMATTER = /^\uFEFF?---\r?\n/;
|
|
898
|
+
const FRONTMATTER_BLOCK = /^\uFEFF?---\r?\n(?:---|([\s\S]*?)\r?\n---)[ \t]*(?:\r?\n|$)/;
|
|
899
|
+
const RULE = /^rules\/([^/]+)\.mdc?$/;
|
|
900
|
+
const MARKDOWN = (dir) => new RegExp(`^${dir}/([^/]+)\\.md$`);
|
|
901
|
+
/** A subagent's or command's Markdown with its frontmatter rewritten by `format`. */
|
|
902
|
+
function reformatted(text, part, what, format) {
|
|
903
|
+
const block = FRONTMATTER_BLOCK.exec(text);
|
|
904
|
+
let front = {};
|
|
905
|
+
if (block) {
|
|
906
|
+
try {
|
|
907
|
+
front = parse(block[1] ?? "") ?? {};
|
|
908
|
+
} catch {
|
|
909
|
+
throw new InfraError(`${what}'s frontmatter isn't valid YAML`);
|
|
910
|
+
}
|
|
911
|
+
if (!isRecord(front)) throw new InfraError(`${what}'s frontmatter isn't a YAML mapping`);
|
|
912
|
+
}
|
|
913
|
+
const body = block ? text.slice(block[0].length).replace(/^\r?\n/, "") : text.replace(/^\uFEFF/, "");
|
|
914
|
+
const out = format(part, front);
|
|
915
|
+
const yaml = Object.keys(out).length ? stringify(out, { lineWidth: 0 }) : "";
|
|
916
|
+
return yaml ? `---\n${yaml}---\n\n${body}` : body;
|
|
917
|
+
}
|
|
918
|
+
/** Cursor's handlers for one plugin's `hooks/hooks.json`, by Cursor's event name. */
|
|
919
|
+
function hookHandlers(plugin, text, hooks) {
|
|
920
|
+
const what = `the ${plugin.name} plugin's hooks/hooks.json`;
|
|
921
|
+
let raw;
|
|
922
|
+
try {
|
|
923
|
+
raw = JSON.parse(text);
|
|
924
|
+
} catch {
|
|
925
|
+
throw new InfraError(`${what} isn't valid JSON`);
|
|
926
|
+
}
|
|
927
|
+
const events = isRecord(raw) && isRecord(raw.hooks) ? raw.hooks : {};
|
|
928
|
+
const out = {};
|
|
929
|
+
for (const [event, groups] of Object.entries(events)) {
|
|
930
|
+
const named = hooks.events[event];
|
|
931
|
+
if (!named) throw new InfraError(`${what} has ${event} hooks, which the agent has no event for`);
|
|
932
|
+
for (const group of Array.isArray(groups) ? groups.filter(isRecord) : []) {
|
|
933
|
+
const matcher = hooks.matcher(event, typeof group.matcher === "string" ? group.matcher : void 0);
|
|
934
|
+
if (matcher === null) continue;
|
|
935
|
+
for (const h of Array.isArray(group.hooks) ? group.hooks.filter(isRecord) : []) {
|
|
936
|
+
if ((h.type ?? "command") !== "command" || typeof h.command !== "string") throw new InfraError(`${what} has a ${event} hook that isn't a command`);
|
|
937
|
+
out[named] ??= [];
|
|
938
|
+
out[named].push({
|
|
939
|
+
type: "command",
|
|
940
|
+
command: h.command.replaceAll(PLUGIN_ROOT, plugin.dir),
|
|
941
|
+
...typeof h.timeout === "number" ? { timeout: h.timeout } : {},
|
|
942
|
+
...matcher === void 0 ? {} : { matcher }
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
return out;
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* The workspace files that give a flat agent the plugins' rules, subagents, commands and hooks,
|
|
951
|
+
* by path relative to the workspace: each part `unpack` names and the plugin keeps, with
|
|
952
|
+
* `${CLAUDE_PLUGIN_ROOT}` filled in with the plugin's `dir`. The hooks of every plugin go in one
|
|
953
|
+
* file, after the handlers of `hooksFile` (the workspace's own, or null).
|
|
954
|
+
*/
|
|
955
|
+
function unpackPlugins(plugins, unpack, hooksFile = null) {
|
|
956
|
+
const out = [];
|
|
957
|
+
let hooks;
|
|
958
|
+
for (const plugin of plugins) {
|
|
959
|
+
const keeps = (part, dir) => dir !== void 0 && plugin.parts.includes(part);
|
|
960
|
+
for (const f of plugin.files) {
|
|
961
|
+
const text = () => Buffer.from(f.content).toString("utf8").replaceAll(PLUGIN_ROOT, plugin.dir);
|
|
962
|
+
const rule = RULE.exec(f.path);
|
|
963
|
+
if (rule && keeps("rules", unpack.rules)) {
|
|
964
|
+
const body = text();
|
|
965
|
+
out.push({
|
|
966
|
+
path: `${unpack.rules}/${plugin.name}-${rule[1]}.mdc`,
|
|
967
|
+
content: FRONTMATTER.test(body) ? body : `---\nalwaysApply: true\n---\n\n${body}`
|
|
968
|
+
});
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
for (const [part, dir] of [["agents", unpack.agents], ["commands", unpack.commands]]) {
|
|
972
|
+
const name = MARKDOWN(part).exec(f.path)?.[1];
|
|
973
|
+
if (!name || !keeps(part, dir)) continue;
|
|
974
|
+
const what = `the ${plugin.name} plugin's ${part === "agents" ? "subagent" : "command"} ${f.path}`;
|
|
975
|
+
out.push({
|
|
976
|
+
path: `${dir}/${name}.md`,
|
|
977
|
+
content: unpack.frontmatter ? reformatted(text(), part, what, unpack.frontmatter) : text()
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
if (f.path === "hooks/hooks.json" && unpack.hooks && plugin.parts.includes("hooks")) {
|
|
981
|
+
if (!hooks) {
|
|
982
|
+
let own = {};
|
|
983
|
+
try {
|
|
984
|
+
own = hooksFile === null ? {} : JSON.parse(hooksFile);
|
|
985
|
+
} catch {
|
|
986
|
+
throw new InfraError(`The workspace's ${unpack.hooks.file} isn't valid JSON, so reeval can't add the plugins' hooks to it`);
|
|
987
|
+
}
|
|
988
|
+
hooks = isRecord(own) ? {
|
|
989
|
+
version: 1,
|
|
990
|
+
...own
|
|
991
|
+
} : { version: 1 };
|
|
992
|
+
}
|
|
993
|
+
const merged = isRecord(hooks.hooks) ? { ...hooks.hooks } : {};
|
|
994
|
+
const raw = Buffer.from(f.content).toString("utf8");
|
|
995
|
+
for (const [event, handlers] of Object.entries(hookHandlers(plugin, raw, unpack.hooks))) merged[event] = [...Array.isArray(merged[event]) ? merged[event] : [], ...handlers];
|
|
996
|
+
hooks.hooks = merged;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
if (hooks && unpack.hooks) out.push({
|
|
1001
|
+
path: unpack.hooks.file,
|
|
1002
|
+
content: `${JSON.stringify(hooks, null, 2)}\n`
|
|
1003
|
+
});
|
|
1004
|
+
return out;
|
|
1005
|
+
}
|
|
1006
|
+
//#endregion
|
|
1380
1007
|
//#region src/sandbox/phases.ts
|
|
1381
1008
|
const strip = ({ phase: _, ...sub }) => sub;
|
|
1382
1009
|
/**
|
|
@@ -1406,6 +1033,70 @@ function brokerPhases(box, subs) {
|
|
|
1406
1033
|
};
|
|
1407
1034
|
}
|
|
1408
1035
|
//#endregion
|
|
1036
|
+
//#region src/attempt/mcp-startup.ts
|
|
1037
|
+
/**
|
|
1038
|
+
* The timer and its log beside the workspace: `/reeval/bin/mcp-timer.cjs` in Docker. `.cjs`, since
|
|
1039
|
+
* a sandbox's directories may be in a `"type": "module"` package.
|
|
1040
|
+
*/
|
|
1041
|
+
const TIMER = "../bin/mcp-timer.cjs";
|
|
1042
|
+
const LOG = "../mcp-startup.jsonl";
|
|
1043
|
+
/** The attempt's error when an MCP server never answered `initialize`. */
|
|
1044
|
+
const mcpTimeoutMessage = (server) => `MCP server ${server} didn't answer initialize within 60s`;
|
|
1045
|
+
/**
|
|
1046
|
+
* Puts the timer in the sandbox and runs every stdio server through it, so whichever process
|
|
1047
|
+
* starts a server (the agent's CLI, once a session or once a turn, or the fake agent) logs when it
|
|
1048
|
+
* answered. Returns the servers unchanged, and no log, when none is stdio, or with `untimed`'s
|
|
1049
|
+
* reason when the sandbox has no `node` (an `image` used as is), rather than break them.
|
|
1050
|
+
*/
|
|
1051
|
+
async function timeMcpServers(box, servers, signal) {
|
|
1052
|
+
if (!Object.values(servers).some((s) => s.transport === "stdio")) return { servers };
|
|
1053
|
+
const found = await box.exec("command -v node", { signal });
|
|
1054
|
+
const node = found.exitCode === 0 ? found.stdout.trim().split("\n")[0] : "";
|
|
1055
|
+
if (!node) return {
|
|
1056
|
+
servers,
|
|
1057
|
+
untimed: "the sandbox has no node to run the timer"
|
|
1058
|
+
};
|
|
1059
|
+
await box.writeFile(TIMER, await readFile(MCP_TIMER, "utf8"));
|
|
1060
|
+
await box.writeFile(LOG, "");
|
|
1061
|
+
const timer = `${box.workdir.replace(/[\\/][^\\/]*$/, "")}/bin/mcp-timer.cjs`;
|
|
1062
|
+
const wrapped = Object.fromEntries(Object.entries(servers).map(([name, s]) => [name, s.transport === "stdio" ? {
|
|
1063
|
+
...s,
|
|
1064
|
+
command: node,
|
|
1065
|
+
args: [
|
|
1066
|
+
timer,
|
|
1067
|
+
"--name",
|
|
1068
|
+
name,
|
|
1069
|
+
"--",
|
|
1070
|
+
s.command,
|
|
1071
|
+
...s.args
|
|
1072
|
+
]
|
|
1073
|
+
} : s]));
|
|
1074
|
+
let seen = 0;
|
|
1075
|
+
return {
|
|
1076
|
+
servers: wrapped,
|
|
1077
|
+
log: { async read() {
|
|
1078
|
+
const complete = (await box.readFile(LOG).catch(() => null) ?? "").split("\n").slice(0, -1);
|
|
1079
|
+
const fresh = complete.slice(seen);
|
|
1080
|
+
seen = complete.length;
|
|
1081
|
+
return fresh.flatMap((line) => {
|
|
1082
|
+
try {
|
|
1083
|
+
const entry = JSON.parse(line);
|
|
1084
|
+
return typeof entry.server === "string" ? [entry] : [];
|
|
1085
|
+
} catch {
|
|
1086
|
+
return [];
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
} }
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
/** Each server start in `entries`, with how long it took to answer `initialize`; null when it didn't. */
|
|
1093
|
+
const mcpStartupOf = (entries) => entries.map((e) => ({
|
|
1094
|
+
server: e.server,
|
|
1095
|
+
ms: e.initializedAt === void 0 ? null : Math.max(0, e.initializedAt - e.spawnedAt)
|
|
1096
|
+
}));
|
|
1097
|
+
/** The first server in `entries` that never answered `initialize`. */
|
|
1098
|
+
const timedOutServer = (entries) => entries.find((e) => e.timeout)?.server;
|
|
1099
|
+
//#endregion
|
|
1409
1100
|
//#region src/attempt/state.ts
|
|
1410
1101
|
/** Extra time an attempt gets, outside its eval timeout, when its image may need building. */
|
|
1411
1102
|
const SETUP_BUDGET_MS = 12e5;
|
|
@@ -1566,6 +1257,131 @@ async function copyWorkspace(box, workspace, signal) {
|
|
|
1566
1257
|
debugLog("env", `workspace ${workspace.label} at ${workspace.commit.slice(0, 12)} (${tar.byteLength} bytes of tar${workspace.init ? ", with a .git" : ""}) in ${formatDuration(performance.now() - start)}`);
|
|
1567
1258
|
}
|
|
1568
1259
|
/**
|
|
1260
|
+
* A built plugin's directory, copied from where its layer left it in the image, less the parts
|
|
1261
|
+
* `only` leaves out, with the manifest as reeval read it.
|
|
1262
|
+
*/
|
|
1263
|
+
async function copyBuiltPlugin(a, box, plugin, dir) {
|
|
1264
|
+
const dropped = droppedPaths(plugin.parts).map((p) => shellQuote(`${dir}/${p}`));
|
|
1265
|
+
const r = await box.exec(`mkdir -p ${shellQuote(dir)} && cp -a ${shellQuote(plugin.built.dir)}/. ${shellQuote(dir)}/${dropped.length ? ` && rm -rf ${dropped.join(" ")}` : ""}`, { signal: a.signal });
|
|
1266
|
+
if (r.exitCode !== 0) throw new InfraError(`Couldn't copy the ${plugin.name} plugin's build from ${plugin.built.tag}: ${tailOf(r.stderr || r.stdout)}`);
|
|
1267
|
+
const manifest = plugin.files.find((f) => f.path === PLUGIN_MANIFEST);
|
|
1268
|
+
if (manifest) await box.writeFile(`${dir}/${PLUGIN_MANIFEST}`, manifest.content);
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Copies each plugin to `PLUGIN_DIR/<name>/` before the workspace snapshot (a built one from its
|
|
1272
|
+
* layer in the image, any other from the host), and points its MCP servers' `${CLAUDE_PLUGIN_ROOT}`
|
|
1273
|
+
* at that directory, for the adapter to load from there.
|
|
1274
|
+
*/
|
|
1275
|
+
async function copyPlugins(a, box, env) {
|
|
1276
|
+
const out = [];
|
|
1277
|
+
for (const plugin of env.plugins) {
|
|
1278
|
+
const at = `${PLUGIN_DIR}/${plugin.name}`;
|
|
1279
|
+
const dir = `${box.home}/${at.slice(2)}`;
|
|
1280
|
+
const { built } = plugin;
|
|
1281
|
+
if (built) await copyBuiltPlugin(a, box, {
|
|
1282
|
+
...plugin,
|
|
1283
|
+
built
|
|
1284
|
+
}, dir);
|
|
1285
|
+
else for (const f of plugin.files) await box.writeFile(`${at}/${f.path}`, f.content);
|
|
1286
|
+
const executable = built ? [] : plugin.files.flatMap((f) => f.executable ? [`${dir}/${f.path}`] : []);
|
|
1287
|
+
if (executable.length) {
|
|
1288
|
+
const r = await box.exec(`chmod +x ${executable.map(shellQuote).join(" ")}`, { signal: a.signal });
|
|
1289
|
+
if (r.exitCode !== 0) throw new InfraError(`Couldn't make the ${plugin.name} plugin's ${executable.length} executable files executable: ${tailOf(r.stderr || r.stdout)}`);
|
|
1290
|
+
}
|
|
1291
|
+
const root = (v) => v.replaceAll(PLUGIN_ROOT, dir);
|
|
1292
|
+
for (const key of plugin.servers) {
|
|
1293
|
+
const server = env.mcp[key];
|
|
1294
|
+
if (server?.transport !== "stdio") continue;
|
|
1295
|
+
env.mcp[key] = {
|
|
1296
|
+
...server,
|
|
1297
|
+
command: root(server.command),
|
|
1298
|
+
args: server.args.map(root),
|
|
1299
|
+
env: {
|
|
1300
|
+
...Object.fromEntries(Object.entries(server.env).map(([k, v]) => [k, root(v)])),
|
|
1301
|
+
[PLUGIN_ROOT_VAR]: dir
|
|
1302
|
+
}
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
out.push({
|
|
1306
|
+
name: plugin.name,
|
|
1307
|
+
dir,
|
|
1308
|
+
parts: plugin.parts,
|
|
1309
|
+
servers: plugin.servers
|
|
1310
|
+
});
|
|
1311
|
+
debugLog("env", `plugin ${plugin.name} → ${at}/ (${built ? `built in ${built.tag}` : `${plugin.files.length} files`}; ${plugin.parts.join(", ")})`);
|
|
1312
|
+
}
|
|
1313
|
+
return out;
|
|
1314
|
+
}
|
|
1315
|
+
/**
|
|
1316
|
+
* The parts a flat agent has unpacked, as a built plugin's copy in the sandbox has them: its
|
|
1317
|
+
* build's output, which `files` (the source before the build) may not match. Builds run on Docker
|
|
1318
|
+
* only, so the sandbox has `find`.
|
|
1319
|
+
*/
|
|
1320
|
+
async function unpackedFromCopy(a, box, dir) {
|
|
1321
|
+
const r = await box.exec(`cd ${shellQuote(dir)} && find rules agents commands hooks -maxdepth 1 -type f 2>/dev/null; true`, { signal: a.signal });
|
|
1322
|
+
const out = [];
|
|
1323
|
+
for (const path of r.stdout.split("\n").filter(Boolean)) {
|
|
1324
|
+
const content = await box.readFile(`${dir}/${path}`);
|
|
1325
|
+
if (content !== null) out.push({
|
|
1326
|
+
path,
|
|
1327
|
+
content: Buffer.from(content)
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
return out;
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Unpacks the plugins' rules, subagents, commands and hooks into the workspace where a flat agent
|
|
1334
|
+
* reads them, before the snapshot, so the diff leaves them out. A file the workspace already has
|
|
1335
|
+
* is an infra error, except the hooks file, whose own handlers the plugins' join.
|
|
1336
|
+
*/
|
|
1337
|
+
async function writeUnpackedPlugins(a, box, env, plugins, unpack) {
|
|
1338
|
+
const placed = [];
|
|
1339
|
+
for (const p of plugins) {
|
|
1340
|
+
const resolved = env.plugins.find((r) => r.name === p.name);
|
|
1341
|
+
const files = resolved?.built ? await unpackedFromCopy(a, box, p.dir) : resolved?.files ?? [];
|
|
1342
|
+
placed.push({
|
|
1343
|
+
...p,
|
|
1344
|
+
files
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
const files = unpackPlugins(placed, unpack, unpack.hooks ? await box.readFile(unpack.hooks.file) : null);
|
|
1348
|
+
const fresh = files.filter((f) => f.path !== unpack.hooks?.file);
|
|
1349
|
+
if (fresh.length) {
|
|
1350
|
+
const taken = (await box.exec(`for p in ${fresh.map((f) => shellQuote(f.path)).join(" ")}; do { [ -e "$p" ] || [ -L "$p" ]; } && echo "$p"; done; true`, { signal: a.signal })).stdout.trim().split("\n").filter(Boolean);
|
|
1351
|
+
if (taken.length) throw new InfraError(`The workspace already has ${taken.join(", ")}, which a plugin would replace: rename the plugin's file, or leave the plugin out of this cell`);
|
|
1352
|
+
}
|
|
1353
|
+
for (const f of files) {
|
|
1354
|
+
await box.writeFile(f.path, f.content);
|
|
1355
|
+
debugLog("env", `plugin part → ${f.path} (unpacked for the agent)`);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* For an agent that starts a plugin's MCP servers from the plugin itself: rewrites each placed
|
|
1360
|
+
* plugin's `.mcp.json` to the servers as reeval resolved them, through the MCP timer, so they're
|
|
1361
|
+
* timed like the environment's. A manifest that declares `mcpServers` is pointed at that file.
|
|
1362
|
+
* Only namespaced layouts, which refuse a plugin server reading a host secret, so no secret value
|
|
1363
|
+
* lands in the file.
|
|
1364
|
+
*/
|
|
1365
|
+
async function writePluginMcp(box, env, plugins, servers) {
|
|
1366
|
+
for (const placed of plugins) {
|
|
1367
|
+
if (!placed.servers.length) continue;
|
|
1368
|
+
const own = Object.fromEntries(placed.servers.flatMap((key) => {
|
|
1369
|
+
const s = servers[key];
|
|
1370
|
+
return s ? [[key.slice(placed.name.length + 1), s]] : [];
|
|
1371
|
+
}));
|
|
1372
|
+
const at = `${PLUGIN_DIR}/${placed.name}`;
|
|
1373
|
+
const json = (v) => `${JSON.stringify(v, null, 2)}\n`;
|
|
1374
|
+
await box.writeFile(`${at}/.mcp.json`, json({ mcpServers: claudeMcpServers(own) }));
|
|
1375
|
+
const manifest = env.plugins.find((p) => p.name === placed.name)?.files.find((f) => f.path === PLUGIN_MANIFEST);
|
|
1376
|
+
const parsed = manifest ? JSON.parse(Buffer.from(manifest.content).toString("utf8")) : {};
|
|
1377
|
+
if (parsed.mcpServers !== void 0) await box.writeFile(`${at}/${PLUGIN_MANIFEST}`, json({
|
|
1378
|
+
...parsed,
|
|
1379
|
+
mcpServers: "./.mcp.json"
|
|
1380
|
+
}));
|
|
1381
|
+
debugLog("env", `plugin ${placed.name}'s MCP servers → ${at}/.mcp.json, through the timer`);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1569
1385
|
* Starts the harness session within its `startTimeout`. A start that doesn't become ready in time
|
|
1570
1386
|
* is aborted (and closed, should it finish later) and started once more: a stuck bootstrap is
|
|
1571
1387
|
* usually transient, and nothing has run yet, so the retry can't change what the attempt
|
|
@@ -1597,7 +1413,7 @@ async function startHarness(a, harness, box, options) {
|
|
|
1597
1413
|
}
|
|
1598
1414
|
/**
|
|
1599
1415
|
* Starts the sandbox (with the brokered secrets its gateway swaps in), writes the
|
|
1600
|
-
* environment's files
|
|
1416
|
+
* environment's files, workspace skills and plugins, runs its setup steps, snapshots the workspace for
|
|
1601
1417
|
* the diff, and starts the harness session with the harness-placed skills and MCP servers.
|
|
1602
1418
|
*/
|
|
1603
1419
|
async function startSession(a, runId) {
|
|
@@ -1611,7 +1427,7 @@ async function startSession(a, runId) {
|
|
|
1611
1427
|
env: env.vars,
|
|
1612
1428
|
image: a.image,
|
|
1613
1429
|
brokering: brokeringFor(spec.harness),
|
|
1614
|
-
expectedBlocked: expectedBlockedFor(spec.harness)?.hosts,
|
|
1430
|
+
expectedBlocked: expectedBlockedFor(spec.harness, env.plugins.length > 0)?.hosts,
|
|
1615
1431
|
substitutions: env.substitutions,
|
|
1616
1432
|
egress: spec.egress
|
|
1617
1433
|
});
|
|
@@ -1643,19 +1459,28 @@ async function startSession(a, runId) {
|
|
|
1643
1459
|
for (const f of files) await box.writeFile(`${skillDir}/${skill.name}/${f.path}`, f.content);
|
|
1644
1460
|
debugLog("env", `skill ${skill.name} → ${skillDir}/${skill.name}/ (${files.length} files, workspace)`);
|
|
1645
1461
|
}
|
|
1462
|
+
const plugins = await copyPlugins(a, box, env);
|
|
1646
1463
|
const layout = layoutOf(spec.harness);
|
|
1464
|
+
if (layout.pluginUnpack && plugins.length) await writeUnpackedPlugins(a, box, env, plugins, layout.pluginUnpack);
|
|
1647
1465
|
for (const skill of env.skills) debugLog("env", `skill ${skill.name} → the harness adapter places it (~/${layout.harnessSkillDir}/${skill.name}/ for ${spec.harness.kind === "fake" ? `the fake ${spec.harness.layout ?? "cursor"} layout` : spec.harness.kind})`);
|
|
1648
1466
|
for (const [name, server] of Object.entries(env.mcp)) debugLog("env", `mcp ${name} (${server.transport}${server.transport === "stdio" ? `: ${server.command} ${server.args.join(" ")}` : `: ${server.url}`}) → the harness adapter writes its MCP config`);
|
|
1649
1467
|
await runSetup(a, box, env);
|
|
1650
1468
|
await snapshot(box, a.signal);
|
|
1651
1469
|
debugLog("env", "workspace snapshot taken: the diff starts here");
|
|
1470
|
+
const timed = await timeMcpServers(box, env.mcp, a.signal);
|
|
1471
|
+
if (timed.log) {
|
|
1472
|
+
a.mcpStartup = timed.log;
|
|
1473
|
+
debugLog("env", "stdio MCP servers start through reeval's MCP timer");
|
|
1474
|
+
} else if (timed.untimed) debugLog("env", `stdio MCP servers start untimed: ${timed.untimed}`);
|
|
1475
|
+
if (layout.pluginPlacement === "namespaced") await writePluginMcp(box, env, plugins, timed.servers);
|
|
1652
1476
|
const harness = await createHarness(spec.harness);
|
|
1653
1477
|
const harnessStart = performance.now();
|
|
1654
1478
|
debugLog("harness", `starting ${harnessId(spec.harness)}: bootstrap and session`);
|
|
1655
1479
|
const session = await startHarness(a, harness, box, {
|
|
1656
1480
|
skills: env.skills,
|
|
1657
1481
|
workspaceSkills: env.workspaceSkills.map((s) => s.name),
|
|
1658
|
-
mcp:
|
|
1482
|
+
mcp: timed.servers,
|
|
1483
|
+
plugins
|
|
1659
1484
|
});
|
|
1660
1485
|
debugLog("harness", `${harnessId(spec.harness)} session ready in ${formatDuration(performance.now() - harnessStart)}`);
|
|
1661
1486
|
a.session = session;
|
|
@@ -1712,6 +1537,46 @@ function cutOff(signal, startedAt, grace = TURN_STOP_GRACE_MS) {
|
|
|
1712
1537
|
cancel
|
|
1713
1538
|
};
|
|
1714
1539
|
}
|
|
1540
|
+
/** How often a turn reads the MCP timer's log for a server that never answered `initialize`. */
|
|
1541
|
+
const MCP_POLL_MS = 2e3;
|
|
1542
|
+
/**
|
|
1543
|
+
* Reads the MCP timer's log through a turn. `signal` aborts as soon as a server has timed out, so
|
|
1544
|
+
* an agent stuck waiting for it stops; `end` takes the turn's last entries.
|
|
1545
|
+
*/
|
|
1546
|
+
function watchMcpStartup(log) {
|
|
1547
|
+
const stop = new AbortController();
|
|
1548
|
+
const entries = [];
|
|
1549
|
+
let timedOut;
|
|
1550
|
+
let at = 0;
|
|
1551
|
+
let reading;
|
|
1552
|
+
const poll = async () => {
|
|
1553
|
+
entries.push(...await log.read());
|
|
1554
|
+
timedOut ??= timedOutServer(entries);
|
|
1555
|
+
if (timedOut && !stop.signal.aborted) {
|
|
1556
|
+
at = Date.now();
|
|
1557
|
+
stop.abort(new InfraError(mcpTimeoutMessage(timedOut)));
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1560
|
+
const timer = setInterval(() => {
|
|
1561
|
+
reading ??= poll().finally(() => {
|
|
1562
|
+
reading = void 0;
|
|
1563
|
+
});
|
|
1564
|
+
}, MCP_POLL_MS);
|
|
1565
|
+
return {
|
|
1566
|
+
signal: stop.signal,
|
|
1567
|
+
close: () => clearInterval(timer),
|
|
1568
|
+
async end() {
|
|
1569
|
+
clearInterval(timer);
|
|
1570
|
+
await reading;
|
|
1571
|
+
await poll();
|
|
1572
|
+
return {
|
|
1573
|
+
entries,
|
|
1574
|
+
timedOut,
|
|
1575
|
+
at: at || Date.now()
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1715
1580
|
/**
|
|
1716
1581
|
* One turn. Sends the prompt to the started session, records the turn as it goes (an
|
|
1717
1582
|
* errored entry first, so a turn that throws is still in the record), takes the agent's context
|
|
@@ -1742,11 +1607,48 @@ function turnSender(a, takeContext, checks) {
|
|
|
1742
1607
|
turn: turnNumber,
|
|
1743
1608
|
prompt
|
|
1744
1609
|
}, `turn ${turnNumber} prompt`);
|
|
1745
|
-
const
|
|
1610
|
+
const watch = a.mcpStartup && watchMcpStartup(a.mcpStartup);
|
|
1611
|
+
const signal = watch ? AbortSignal.any([a.signal, watch.signal]) : a.signal;
|
|
1612
|
+
const { cutoff, cancel } = cutOff(signal, entry.startedAt);
|
|
1746
1613
|
const sendStarted = Date.now();
|
|
1747
|
-
const
|
|
1614
|
+
const sending = agent.send(prompt, signal).catch((error) => {
|
|
1615
|
+
if (!watch?.signal.aborted || a.signal.aborted) throw error;
|
|
1616
|
+
return {
|
|
1617
|
+
text: "",
|
|
1618
|
+
status: "aborted",
|
|
1619
|
+
transcript: [],
|
|
1620
|
+
usage: null,
|
|
1621
|
+
model: null,
|
|
1622
|
+
unreported: [],
|
|
1623
|
+
startedAt: entry.startedAt,
|
|
1624
|
+
durationMs: Date.now() - sendStarted
|
|
1625
|
+
};
|
|
1626
|
+
});
|
|
1627
|
+
const { raw, ...sent } = await a.timed("agentMs", () => Promise.race([sending, cutoff])).catch(async (error) => {
|
|
1628
|
+
if (watch) entry.mcpStartup = mcpStartupOf((await watch.end()).entries);
|
|
1629
|
+
throw error;
|
|
1630
|
+
}).finally(() => {
|
|
1631
|
+
cancel();
|
|
1632
|
+
watch?.close();
|
|
1633
|
+
});
|
|
1748
1634
|
const sendWallMs = Date.now() - sendStarted;
|
|
1749
|
-
|
|
1635
|
+
const startup = await watch?.end();
|
|
1636
|
+
const timedOut = startup?.timedOut && mcpTimeoutMessage(startup.timedOut);
|
|
1637
|
+
const seenAt = startup?.at ?? Date.now();
|
|
1638
|
+
const result = timedOut ? {
|
|
1639
|
+
...sent,
|
|
1640
|
+
status: "error",
|
|
1641
|
+
transcript: [...sent.transcript, {
|
|
1642
|
+
type: "error",
|
|
1643
|
+
message: timedOut,
|
|
1644
|
+
ts: new Date(seenAt).toISOString(),
|
|
1645
|
+
atMs: Math.max(0, seenAt - Date.parse(sent.startedAt))
|
|
1646
|
+
}].sort((x, y) => x.atMs - y.atMs)
|
|
1647
|
+
} : sent;
|
|
1648
|
+
Object.assign(entry, result, raw ? { raw } : {}, {
|
|
1649
|
+
durationMs: Math.max(result.durationMs, sendWallMs),
|
|
1650
|
+
...startup ? { mcpStartup: mcpStartupOf(startup.entries) } : {}
|
|
1651
|
+
});
|
|
1750
1652
|
for (const event of result.transcript) debugTrace("harness", "transcript", {
|
|
1751
1653
|
turn: turnNumber,
|
|
1752
1654
|
...event
|
|
@@ -1765,6 +1667,12 @@ function turnSender(a, takeContext, checks) {
|
|
|
1765
1667
|
});
|
|
1766
1668
|
const plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
1767
1669
|
debugLog("harness", `turn ${turnNumber} ${result.status} in ${formatDuration(result.durationMs)} · ${plural(count("tool", "web-search", "web-fetch"), "tool call")} · ${plural(count("file-edit"), "file edit")}${failure && "message" in failure ? ` · error: ${clip(String(failure.message), 200)}` : ""}${result.model ? ` · model ${result.model}` : ""}`);
|
|
1670
|
+
for (const s of entry.mcpStartup ?? []) debugLog("harness", `turn ${turnNumber} mcp ${s.server} ${s.ms === null ? "didn't answer initialize" : `answered initialize in ${formatDuration(s.ms)}`}`);
|
|
1671
|
+
if (timedOut) {
|
|
1672
|
+
const error = new InfraError(timedOut);
|
|
1673
|
+
a.startFailure ??= { error };
|
|
1674
|
+
throw error;
|
|
1675
|
+
}
|
|
1768
1676
|
a.signal.throwIfAborted();
|
|
1769
1677
|
const diff = await a.timed("diffMs", () => diffSinceSnapshot(box, a.signal));
|
|
1770
1678
|
a.record.diff = diff;
|
|
@@ -1996,11 +1904,18 @@ function planEval(declaration, ctx) {
|
|
|
1996
1904
|
const [first] = unbrokered(resolved.env, plannedAttempts);
|
|
1997
1905
|
if (first) throw new UsageError(unbrokeredError(first, where, ctx.config.file));
|
|
1998
1906
|
}
|
|
1907
|
+
const pluginNotes = [];
|
|
1908
|
+
const pluginLabels = /* @__PURE__ */ new Map();
|
|
1999
1909
|
for (const planned of plannedAttempts) {
|
|
2000
1910
|
const why = unenforceable(planned.egress, planned.sandbox, where);
|
|
2001
1911
|
if (why) throw new UsageError(why);
|
|
1912
|
+
const on = `${where} on ${harnessId(planned.harness)}`;
|
|
2002
1913
|
const mcp = refusedMcp(planned.harness, planned.environment.layers);
|
|
2003
|
-
if (mcp) throw new UsageError(`${
|
|
1914
|
+
if (mcp) throw new UsageError(`${on}: ${mcp}`);
|
|
1915
|
+
const plugins = planPlugins(planned, ctx.config.root);
|
|
1916
|
+
if (plugins.refusal) throw new UsageError(`${on}: ${plugins.refusal}`);
|
|
1917
|
+
for (const note of plugins.notes) if (!pluginNotes.includes(note)) pluginNotes.push(note);
|
|
1918
|
+
if (plugins.labels.length) pluginLabels.set(planned, plugins.labels);
|
|
2004
1919
|
}
|
|
2005
1920
|
const path = ctx.file ? relativeFile(ctx.config.root, ctx.file) : null;
|
|
2006
1921
|
const rankFile = relativeFile(ctx.config.root, ctx.file ?? "");
|
|
@@ -2013,6 +1928,7 @@ function planEval(declaration, ctx) {
|
|
|
2013
1928
|
configFile: ctx.config.file,
|
|
2014
1929
|
file: path
|
|
2015
1930
|
});
|
|
1931
|
+
plan.notes.push(...pluginNotes);
|
|
2016
1932
|
const setByOf = (planned) => ({
|
|
2017
1933
|
sandbox: sandboxSetBy(planned, resolved, ctx.config.file),
|
|
2018
1934
|
harness: harnessBy
|
|
@@ -2048,7 +1964,11 @@ function planEval(declaration, ctx) {
|
|
|
2048
1964
|
if (!cells.includes(planned.cell.key)) cells.push(planned.cell.key);
|
|
2049
1965
|
const workspace = workspaceOf(planned.environment.layers);
|
|
2050
1966
|
const files = filesOf(planned.environment.layers);
|
|
2051
|
-
const
|
|
1967
|
+
const builds = builtPlugins(planned.environment.layers);
|
|
1968
|
+
const setBy = builds.length ? {
|
|
1969
|
+
...setByOf(planned),
|
|
1970
|
+
pluginBuilds: `environment ${planned.environment.name}${path ? ` in ${path}` : ""}`
|
|
1971
|
+
} : setByOf(planned);
|
|
2052
1972
|
const id = evalId(rankFile, [...groups, name]);
|
|
2053
1973
|
const key = attemptKey(id, planned.cell.key, planned.n);
|
|
2054
1974
|
const skip = ctx.completed?.includes(key) ? "already completed in this run" : void 0;
|
|
@@ -2069,6 +1989,8 @@ function planEval(declaration, ctx) {
|
|
|
2069
1989
|
egress: planned.egress,
|
|
2070
1990
|
...workspace ? { workspace } : {},
|
|
2071
1991
|
...files.length ? { files } : {},
|
|
1992
|
+
...pluginLabels.has(planned) ? { plugins: pluginLabels.get(planned) } : {},
|
|
1993
|
+
...builds.length ? { pluginBuilds: builds } : {},
|
|
2072
1994
|
plan: skip ? {
|
|
2073
1995
|
...plan,
|
|
2074
1996
|
skip
|
|
@@ -2108,6 +2030,61 @@ function planEval(declaration, ctx) {
|
|
|
2108
2030
|
};
|
|
2109
2031
|
});
|
|
2110
2032
|
}
|
|
2033
|
+
/**
|
|
2034
|
+
* What the plan can tell of an attempt's plugins before any is fetched: a build outside Docker, a
|
|
2035
|
+
* local plugin's content, and the harness's refusal, as a refusal; the commands that fetch a
|
|
2036
|
+
* package at start, as notes under `allow-all`; and a label for each plugin. Git and npm plugins
|
|
2037
|
+
* are read when the attempt resolves its environment, which refuses them the same way.
|
|
2038
|
+
*/
|
|
2039
|
+
function planPlugins(planned, root) {
|
|
2040
|
+
const { layers } = planned.environment;
|
|
2041
|
+
const sources = layeredPlugins(layers);
|
|
2042
|
+
if (sources.length === 0) return {
|
|
2043
|
+
notes: [],
|
|
2044
|
+
labels: []
|
|
2045
|
+
};
|
|
2046
|
+
const read = [];
|
|
2047
|
+
const unread = [];
|
|
2048
|
+
const labels = [];
|
|
2049
|
+
for (const source of sources) {
|
|
2050
|
+
const call = pluginCall(source, root);
|
|
2051
|
+
const p = localPlugin(source, root);
|
|
2052
|
+
if (p instanceof PluginProblem) return {
|
|
2053
|
+
refusal: p.message,
|
|
2054
|
+
notes: [],
|
|
2055
|
+
labels
|
|
2056
|
+
};
|
|
2057
|
+
const build = buildRefusal(source, planned.sandbox, p?.name, root);
|
|
2058
|
+
if (build) return {
|
|
2059
|
+
refusal: build,
|
|
2060
|
+
notes: [],
|
|
2061
|
+
labels
|
|
2062
|
+
};
|
|
2063
|
+
if (p) read.push(p);
|
|
2064
|
+
else unread.push(call);
|
|
2065
|
+
labels.push(p ? `${p.name} (${p.kept.join(", ")})` : pluginSourceLabel(source, root));
|
|
2066
|
+
}
|
|
2067
|
+
const twice = duplicateLocalPlugin(layers, root);
|
|
2068
|
+
if (twice) return {
|
|
2069
|
+
refusal: twice,
|
|
2070
|
+
notes: [],
|
|
2071
|
+
labels
|
|
2072
|
+
};
|
|
2073
|
+
const own = {
|
|
2074
|
+
skills: localSkills(layers, root),
|
|
2075
|
+
mcp: [...new Set(layers.flatMap((l) => Object.keys(l.spec.mcp ?? {})))]
|
|
2076
|
+
};
|
|
2077
|
+
const refusal = refusedPlugins(planned.harness, read, own, unread);
|
|
2078
|
+
if (refusal) return {
|
|
2079
|
+
refusal,
|
|
2080
|
+
notes: [],
|
|
2081
|
+
labels
|
|
2082
|
+
};
|
|
2083
|
+
return {
|
|
2084
|
+
...fetchesAtStart(read, allowedHosts(planned.egress)),
|
|
2085
|
+
labels
|
|
2086
|
+
};
|
|
2087
|
+
}
|
|
2111
2088
|
/** The run-wide cap, plus the sandbox provider's and harness's own caps when they set one. */
|
|
2112
2089
|
function attemptLimits(planned, concurrency) {
|
|
2113
2090
|
const limits = { attempts: concurrency };
|
|
@@ -2348,4 +2325,4 @@ function optionsFrom(written, where, base) {
|
|
|
2348
2325
|
//#endregion
|
|
2349
2326
|
export { assertionRecords as i, evaluate as n, group as r, declareTree as t };
|
|
2350
2327
|
|
|
2351
|
-
//# sourceMappingURL=evaluate-
|
|
2328
|
+
//# sourceMappingURL=evaluate-CXQ1o-pd.js.map
|