@theokit/sdk 4.37.2 → 4.38.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/CHANGELOG.md +72 -72
- package/dist/agent-2GZC4W2A.cjs +51 -0
- package/dist/{agent-IE6IE4NW.cjs.map → agent-2GZC4W2A.cjs.map} +1 -1
- package/dist/agent-3H3UKD6U.js +42 -0
- package/dist/{agent-DW2J37IH.js.map → agent-3H3UKD6U.js.map} +1 -1
- package/dist/{agent-SmxntivO.d.ts → agent-C3AEtM-W.d.ts} +39 -37
- package/dist/{agent-DGv1-d81.d.cts → agent-U3lBX-5t.d.cts} +39 -37
- package/dist/agent.d.ts +1 -1
- package/dist/auth/index.cjs +15 -15
- package/dist/auth/index.js +4 -4
- package/dist/{batch-SY5GZLFD.js → batch-LXSALQC3.js} +9 -9
- package/dist/{batch-SY5GZLFD.js.map → batch-LXSALQC3.js.map} +1 -1
- package/dist/{batch-3SEPDXCU.cjs → batch-NZGN6SFV.cjs} +23 -23
- package/dist/{batch-3SEPDXCU.cjs.map → batch-NZGN6SFV.cjs.map} +1 -1
- package/dist/{chunk-D4ZUJTFB.cjs → chunk-2K7PRHDB.cjs} +8 -8
- package/dist/{chunk-D4ZUJTFB.cjs.map → chunk-2K7PRHDB.cjs.map} +1 -1
- package/dist/chunk-2R6NBT5F.cjs +112 -0
- package/dist/chunk-2R6NBT5F.cjs.map +1 -0
- package/dist/{chunk-2423XDPT.cjs → chunk-3BSP7LSG.cjs} +26 -26
- package/dist/{chunk-2423XDPT.cjs.map → chunk-3BSP7LSG.cjs.map} +1 -1
- package/dist/{chunk-6EDQBX7U.cjs → chunk-3MUIYMUG.cjs} +17 -17
- package/dist/{chunk-6EDQBX7U.cjs.map → chunk-3MUIYMUG.cjs.map} +1 -1
- package/dist/{chunk-P34DHHYQ.cjs → chunk-3VKTDQMX.cjs} +34 -27
- package/dist/chunk-3VKTDQMX.cjs.map +1 -0
- package/dist/{chunk-77WHPVXE.js → chunk-3XKVRLR2.js} +4 -4
- package/dist/chunk-3XKVRLR2.js.map +1 -0
- package/dist/{chunk-SWIBQFPH.cjs → chunk-3XRXORBS.cjs} +4 -4
- package/dist/{chunk-SWIBQFPH.cjs.map → chunk-3XRXORBS.cjs.map} +1 -1
- package/dist/{chunk-DCJ5MCJ3.js → chunk-4APWS2YF.js} +7 -7
- package/dist/{chunk-DCJ5MCJ3.js.map → chunk-4APWS2YF.js.map} +1 -1
- package/dist/{chunk-56ZM6P7Z.cjs → chunk-4MWBKBS4.cjs} +12 -12
- package/dist/chunk-4MWBKBS4.cjs.map +1 -0
- package/dist/chunk-4NVV3GRU.js +110 -0
- package/dist/chunk-4NVV3GRU.js.map +1 -0
- package/dist/{chunk-QGDKZDPN.js → chunk-4WIJSKFA.js} +3 -3
- package/dist/{chunk-QGDKZDPN.js.map → chunk-4WIJSKFA.js.map} +1 -1
- package/dist/{chunk-VNNK6AKU.js → chunk-5BVBNE2F.js} +3 -3
- package/dist/chunk-5BVBNE2F.js.map +1 -0
- package/dist/{chunk-EEZ7CFMM.js → chunk-5KOW3GLE.js} +2 -2
- package/dist/chunk-5KOW3GLE.js.map +1 -0
- package/dist/{chunk-FK4EJD2I.cjs → chunk-6OIO4FYX.cjs} +7 -7
- package/dist/{chunk-FK4EJD2I.cjs.map → chunk-6OIO4FYX.cjs.map} +1 -1
- package/dist/{chunk-IEUDKUPM.cjs → chunk-6OKPJZMX.cjs} +8 -8
- package/dist/{chunk-IEUDKUPM.cjs.map → chunk-6OKPJZMX.cjs.map} +1 -1
- package/dist/{chunk-QFMXXUUT.js → chunk-6XZBKFQ7.js} +3 -3
- package/dist/{chunk-QFMXXUUT.js.map → chunk-6XZBKFQ7.js.map} +1 -1
- package/dist/{chunk-H5YVJB4K.cjs → chunk-6ZGD7R27.cjs} +37 -37
- package/dist/chunk-6ZGD7R27.cjs.map +1 -0
- package/dist/{chunk-HEN5WXXI.js → chunk-7QDTZJRV.js} +6 -6
- package/dist/{chunk-HEN5WXXI.js.map → chunk-7QDTZJRV.js.map} +1 -1
- package/dist/{chunk-MHCKWQR3.cjs → chunk-A3MHE2MQ.cjs} +8 -8
- package/dist/{chunk-MHCKWQR3.cjs.map → chunk-A3MHE2MQ.cjs.map} +1 -1
- package/dist/{chunk-5SB4V74H.js → chunk-A4XXV2C6.js} +18 -18
- package/dist/chunk-A4XXV2C6.js.map +1 -0
- package/dist/{chunk-6VP3XGEK.js → chunk-A6GXYF2Q.js} +9 -9
- package/dist/{chunk-6VP3XGEK.js.map → chunk-A6GXYF2Q.js.map} +1 -1
- package/dist/{chunk-SDIBMLL5.js → chunk-ACHGT3D2.js} +8 -8
- package/dist/{chunk-SDIBMLL5.js.map → chunk-ACHGT3D2.js.map} +1 -1
- package/dist/{chunk-XAEAMI5H.js → chunk-AOZYORH7.js} +4 -4
- package/dist/chunk-AOZYORH7.js.map +1 -0
- package/dist/{chunk-LTCOPATU.cjs → chunk-BE3W4HYV.cjs} +2 -2
- package/dist/chunk-BE3W4HYV.cjs.map +1 -0
- package/dist/{chunk-NQ4AC2U4.cjs → chunk-BT6DFWZB.cjs} +9 -9
- package/dist/{chunk-NQ4AC2U4.cjs.map → chunk-BT6DFWZB.cjs.map} +1 -1
- package/dist/{chunk-XAYVHS2B.cjs → chunk-BUGVDCI5.cjs} +4 -4
- package/dist/{chunk-XAYVHS2B.cjs.map → chunk-BUGVDCI5.cjs.map} +1 -1
- package/dist/{chunk-IT3PWBW4.cjs → chunk-BURYFNOM.cjs} +2 -2
- package/dist/chunk-BURYFNOM.cjs.map +1 -0
- package/dist/{chunk-NNSKJCBM.cjs → chunk-D3SJLXYZ.cjs} +10 -10
- package/dist/{chunk-NNSKJCBM.cjs.map → chunk-D3SJLXYZ.cjs.map} +1 -1
- package/dist/{chunk-MRODLUGY.cjs → chunk-ED7ANP6W.cjs} +4 -4
- package/dist/{chunk-MRODLUGY.cjs.map → chunk-ED7ANP6W.cjs.map} +1 -1
- package/dist/{chunk-MXSMAGDU.js → chunk-EDI7DBWO.js} +33 -26
- package/dist/chunk-EDI7DBWO.js.map +1 -0
- package/dist/{chunk-T4QWDOG6.js → chunk-EE24WJJR.js} +5 -5
- package/dist/{chunk-T4QWDOG6.js.map → chunk-EE24WJJR.js.map} +1 -1
- package/dist/{chunk-ZJN6NTHE.js → chunk-EUB7YLU2.js} +3 -3
- package/dist/{chunk-ZJN6NTHE.js.map → chunk-EUB7YLU2.js.map} +1 -1
- package/dist/{chunk-ALU3KC4G.cjs → chunk-G7X2Y7Y2.cjs} +156 -156
- package/dist/chunk-G7X2Y7Y2.cjs.map +1 -0
- package/dist/{chunk-YCXMSDSP.js → chunk-HHUDNTZC.js} +4 -4
- package/dist/{chunk-YCXMSDSP.js.map → chunk-HHUDNTZC.js.map} +1 -1
- package/dist/{chunk-GL7CFBVC.js → chunk-HI6G3SNY.js} +48 -48
- package/dist/chunk-HI6G3SNY.js.map +1 -0
- package/dist/{chunk-G6AASL5T.cjs → chunk-HLK35TD7.cjs} +24 -24
- package/dist/{chunk-G6AASL5T.cjs.map → chunk-HLK35TD7.cjs.map} +1 -1
- package/dist/{chunk-4YB7B3RP.cjs → chunk-IBDE4R5L.cjs} +13 -13
- package/dist/{chunk-4YB7B3RP.cjs.map → chunk-IBDE4R5L.cjs.map} +1 -1
- package/dist/{chunk-WDGCZDRG.js → chunk-JCXIDFRI.js} +3 -3
- package/dist/{chunk-WDGCZDRG.js.map → chunk-JCXIDFRI.js.map} +1 -1
- package/dist/{chunk-I35DI7ES.js → chunk-JI34KBNC.js} +3 -3
- package/dist/{chunk-I35DI7ES.js.map → chunk-JI34KBNC.js.map} +1 -1
- package/dist/{chunk-TAPQWDW2.js → chunk-KCZW75PI.js} +2 -2
- package/dist/chunk-KCZW75PI.js.map +1 -0
- package/dist/{chunk-U2WLFOA3.cjs → chunk-KL7E244K.cjs} +4 -4
- package/dist/{chunk-U2WLFOA3.cjs.map → chunk-KL7E244K.cjs.map} +1 -1
- package/dist/{chunk-PAEJPLVN.cjs → chunk-KTXOHCAE.cjs} +4 -4
- package/dist/{chunk-PAEJPLVN.cjs.map → chunk-KTXOHCAE.cjs.map} +1 -1
- package/dist/{chunk-2WFB5ZOX.js → chunk-KWXC6ERT.js} +5 -5
- package/dist/{chunk-2WFB5ZOX.js.map → chunk-KWXC6ERT.js.map} +1 -1
- package/dist/{chunk-56OXYUZE.js → chunk-LGCEYEU2.js} +3 -3
- package/dist/chunk-LGCEYEU2.js.map +1 -0
- package/dist/{chunk-FAY3QDGC.cjs → chunk-M4OGSS66.cjs} +11 -11
- package/dist/{chunk-FAY3QDGC.cjs.map → chunk-M4OGSS66.cjs.map} +1 -1
- package/dist/{chunk-F6Y4IEQM.js → chunk-M6UUTNDT.js} +4 -4
- package/dist/{chunk-F6Y4IEQM.js.map → chunk-M6UUTNDT.js.map} +1 -1
- package/dist/{chunk-GM7QDQ24.cjs → chunk-MXRBNJ6D.cjs} +11 -11
- package/dist/{chunk-GM7QDQ24.cjs.map → chunk-MXRBNJ6D.cjs.map} +1 -1
- package/dist/{chunk-YPQF26X7.cjs → chunk-OBA6YK2R.cjs} +2 -2
- package/dist/chunk-OBA6YK2R.cjs.map +1 -0
- package/dist/{chunk-BJMKQOY2.cjs → chunk-OFWU6UKF.cjs} +11 -11
- package/dist/{chunk-BJMKQOY2.cjs.map → chunk-OFWU6UKF.cjs.map} +1 -1
- package/dist/{chunk-3O2ELBST.cjs → chunk-PG3QQLWS.cjs} +5 -5
- package/dist/{chunk-3O2ELBST.cjs.map → chunk-PG3QQLWS.cjs.map} +1 -1
- package/dist/{chunk-2ONFGMBV.js → chunk-PY2RQOA7.js} +2 -2
- package/dist/chunk-PY2RQOA7.js.map +1 -0
- package/dist/{chunk-UQPDFW4E.js → chunk-QFWQLXCL.js} +3 -3
- package/dist/{chunk-UQPDFW4E.js.map → chunk-QFWQLXCL.js.map} +1 -1
- package/dist/{chunk-YBFDBOUN.cjs → chunk-QJQ2MAZF.cjs} +8 -8
- package/dist/{chunk-YBFDBOUN.cjs.map → chunk-QJQ2MAZF.cjs.map} +1 -1
- package/dist/{chunk-QUFSDDNU.cjs → chunk-R4AMI7KD.cjs} +15 -15
- package/dist/{chunk-QUFSDDNU.cjs.map → chunk-R4AMI7KD.cjs.map} +1 -1
- package/dist/{chunk-CSZRJ4RE.js → chunk-SGTMDQ4O.js} +5 -5
- package/dist/{chunk-CSZRJ4RE.js.map → chunk-SGTMDQ4O.js.map} +1 -1
- package/dist/{chunk-3ILFUXNE.cjs → chunk-SRIF7HMN.cjs} +5 -5
- package/dist/{chunk-3ILFUXNE.cjs.map → chunk-SRIF7HMN.cjs.map} +1 -1
- package/dist/{chunk-THEYGCYS.cjs → chunk-TKDWD3MU.cjs} +4 -4
- package/dist/chunk-TKDWD3MU.cjs.map +1 -0
- package/dist/{chunk-ZW345YOI.js → chunk-UD2T7ISG.js} +4 -4
- package/dist/{chunk-ZW345YOI.js.map → chunk-UD2T7ISG.js.map} +1 -1
- package/dist/{chunk-EXQV452X.js → chunk-UWEALKAN.js} +3 -3
- package/dist/{chunk-EXQV452X.js.map → chunk-UWEALKAN.js.map} +1 -1
- package/dist/{chunk-W52DKZBR.cjs → chunk-UZBA6YYP.cjs} +454 -333
- package/dist/chunk-UZBA6YYP.cjs.map +1 -0
- package/dist/{chunk-VYJSHGAY.js → chunk-VUDH4D2M.js} +201 -80
- package/dist/chunk-VUDH4D2M.js.map +1 -0
- package/dist/{chunk-W4QN5CMG.cjs → chunk-VVMLMKYZ.cjs} +9 -9
- package/dist/{chunk-W4QN5CMG.cjs.map → chunk-VVMLMKYZ.cjs.map} +1 -1
- package/dist/{chunk-JNIWFCKI.js → chunk-VYD6BGKK.js} +3 -3
- package/dist/{chunk-JNIWFCKI.js.map → chunk-VYD6BGKK.js.map} +1 -1
- package/dist/{chunk-YHZXBDAW.js → chunk-XJ5TMOGA.js} +16 -16
- package/dist/{chunk-YHZXBDAW.js.map → chunk-XJ5TMOGA.js.map} +1 -1
- package/dist/{chunk-7DXLNNKH.cjs → chunk-XKBJT7WI.cjs} +4 -4
- package/dist/{chunk-7DXLNNKH.cjs.map → chunk-XKBJT7WI.cjs.map} +1 -1
- package/dist/{chunk-YDLNI7J6.js → chunk-XS7SKY2F.js} +3 -3
- package/dist/{chunk-YDLNI7J6.js.map → chunk-XS7SKY2F.js.map} +1 -1
- package/dist/{chunk-NFCKEBHI.js → chunk-YCHC45BY.js} +3 -3
- package/dist/{chunk-NFCKEBHI.js.map → chunk-YCHC45BY.js.map} +1 -1
- package/dist/{chunk-ICYBIC6N.cjs → chunk-YVI4YPUP.cjs} +4 -4
- package/dist/chunk-YVI4YPUP.cjs.map +1 -0
- package/dist/{chunk-TQI3KRXZ.cjs → chunk-ZET34IZE.cjs} +18 -18
- package/dist/{chunk-TQI3KRXZ.cjs.map → chunk-ZET34IZE.cjs.map} +1 -1
- package/dist/{chunk-GLUWH6JO.js → chunk-ZFZYPLQI.js} +4 -4
- package/dist/{chunk-GLUWH6JO.js.map → chunk-ZFZYPLQI.js.map} +1 -1
- package/dist/{chunk-M5ZQBOIZ.js → chunk-ZK3BJ4XQ.js} +3 -3
- package/dist/{chunk-M5ZQBOIZ.js.map → chunk-ZK3BJ4XQ.js.map} +1 -1
- package/dist/{chunk-CD73YHEH.js → chunk-ZYOHF37V.js} +3 -3
- package/dist/{chunk-CD73YHEH.js.map → chunk-ZYOHF37V.js.map} +1 -1
- package/dist/compact-session-RR3ILTES.js +17 -0
- package/dist/{compact-session-WNKKIIDW.js.map → compact-session-RR3ILTES.js.map} +1 -1
- package/dist/compact-session-YOWJJPPB.cjs +54 -0
- package/dist/{compact-session-H5TKLD55.cjs.map → compact-session-YOWJJPPB.cjs.map} +1 -1
- package/dist/compaction.cjs +19 -19
- package/dist/compaction.d.cts +9 -9
- package/dist/compaction.d.ts +9 -9
- package/dist/compaction.js +3 -3
- package/dist/concurrency.cjs +6 -6
- package/dist/concurrency.js +4 -4
- package/dist/context-Q6ISOCN2.cjs +22 -0
- package/dist/{context-ZV33V4DC.cjs.map → context-Q6ISOCN2.cjs.map} +1 -1
- package/dist/context-ZDBBPIHY.js +5 -0
- package/dist/{context-VAYLPT4O.js.map → context-ZDBBPIHY.js.map} +1 -1
- package/dist/{cron-BiK_aNJI.d.cts → cron-BdXOZco9.d.cts} +2 -2
- package/dist/{cron-Dx7B-3WB.d.ts → cron-ChQ6zcW2.d.ts} +2 -2
- package/dist/cron.cjs +29 -29
- package/dist/cron.d.cts +3 -3
- package/dist/cron.d.ts +3 -3
- package/dist/cron.js +28 -28
- package/dist/{errors-DyVbXdR4.d.cts → errors-BUVjJGJA.d.cts} +1 -1
- package/dist/{errors-Cua4WC9H.d.ts → errors-CZIUKLHt.d.ts} +1 -1
- package/dist/errors.cjs +21 -21
- package/dist/errors.d.cts +2 -2
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +2 -2
- package/dist/eval.cjs +32 -32
- package/dist/eval.js +28 -28
- package/dist/{executor-PAH72CB5.js → executor-ASDDIM2X.js} +7 -7
- package/dist/{executor-PAH72CB5.js.map → executor-ASDDIM2X.js.map} +1 -1
- package/dist/{executor-OZSYXDKF.cjs → executor-DPCBSI35.cjs} +23 -23
- package/dist/{executor-OZSYXDKF.cjs.map → executor-DPCBSI35.cjs.map} +1 -1
- package/dist/filesystem/index.cjs +7 -7
- package/dist/filesystem/index.js +3 -3
- package/dist/fs-session-store-K5QUPNJZ.js +10 -0
- package/dist/{fs-session-store-6MY7PNI2.js.map → fs-session-store-K5QUPNJZ.js.map} +1 -1
- package/dist/fs-session-store-T5ZLQX42.cjs +19 -0
- package/dist/{fs-session-store-7E7UPLF2.cjs.map → fs-session-store-T5ZLQX42.cjs.map} +1 -1
- package/dist/generate-object-F56VIZ6U.cjs +20 -0
- package/dist/{generate-object-5SCEH2EE.cjs.map → generate-object-F56VIZ6U.cjs.map} +1 -1
- package/dist/generate-object-LPSO6GVW.js +7 -0
- package/dist/{generate-object-I7UD4NWN.js.map → generate-object-LPSO6GVW.js.map} +1 -1
- package/dist/index-manager-DBJ55ZSO.cjs +19 -0
- package/dist/{index-manager-R6ZJFEZF.cjs.map → index-manager-DBJ55ZSO.cjs.map} +1 -1
- package/dist/index-manager-F2H2RCZU.js +10 -0
- package/dist/{index-manager-KKAK7NT3.js.map → index-manager-F2H2RCZU.js.map} +1 -1
- package/dist/index.cjs +140 -140
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +42 -42
- package/dist/index.d.ts +42 -42
- package/dist/index.js +54 -54
- package/dist/index.js.map +1 -1
- package/dist/{inject-session-DM4SKTZO.cjs → inject-session-25EC74WV.cjs} +10 -10
- package/dist/{inject-session-DM4SKTZO.cjs.map → inject-session-25EC74WV.cjs.map} +1 -1
- package/dist/{inject-session-UXNJQTZ6.js → inject-session-RKNLUE72.js} +7 -7
- package/dist/{inject-session-UXNJQTZ6.js.map → inject-session-RKNLUE72.js.map} +1 -1
- package/dist/internal/agent-loop/live-events.d.ts +46 -0
- package/dist/internal/budget/compute-cost.d.ts +1 -1
- package/dist/internal/diagnostics.d.ts +31 -29
- package/dist/internal/error-mappers/anthropic.d.ts +1 -1
- package/dist/internal/global-singleton.d.ts +7 -7
- package/dist/internal/judge/judge-call.d.ts +14 -14
- package/dist/internal/llm/retrying-client.d.ts +29 -29
- package/dist/internal/llm/transport-error.d.ts +2 -2
- package/dist/internal/persistence/exclusive-create.d.cts +2 -2
- package/dist/internal/persistence/exclusive-create.d.ts +2 -2
- package/dist/internal/persistence/fs-session-store.d.cts +18 -18
- package/dist/internal/persistence/fs-session-store.d.ts +18 -18
- package/dist/internal/persistence/index.cjs +25 -25
- package/dist/internal/persistence/index.cjs.map +1 -1
- package/dist/internal/persistence/index.js +6 -6
- package/dist/internal/persistence/index.js.map +1 -1
- package/dist/internal/persistence/paths.d.cts +7 -7
- package/dist/internal/persistence/paths.d.ts +7 -7
- package/dist/internal/persistence/session-transcript.d.cts +7 -7
- package/dist/internal/persistence/session-transcript.d.ts +7 -7
- package/dist/internal/persistence/session-writer.d.cts +30 -13
- package/dist/internal/persistence/session-writer.d.ts +30 -13
- package/dist/internal/runtime/fixtures/fixture-run-base.d.ts +45 -13
- package/dist/internal/runtime/lifecycle/goal-marker.d.ts +8 -8
- package/dist/internal/security/index.cjs +13 -13
- package/dist/internal/security/index.js +3 -3
- package/dist/internal/session/agent-session.d.ts +14 -14
- package/dist/internal/session/session-cache.d.ts +9 -9
- package/dist/judge-call-3S3T6O2Z.js +5 -0
- package/dist/{judge-call-TAEIXMYG.js.map → judge-call-3S3T6O2Z.js.map} +1 -1
- package/dist/judge-call-XIQJ5GIE.cjs +22 -0
- package/dist/{judge-call-SP3G3X2Y.cjs.map → judge-call-XIQJ5GIE.cjs.map} +1 -1
- package/dist/models.cjs +20 -20
- package/dist/models.cjs.map +1 -1
- package/dist/models.js +5 -5
- package/dist/models.js.map +1 -1
- package/dist/path-safety.cjs +10 -10
- package/dist/path-safety.js +3 -3
- package/dist/persistence.cjs +34 -34
- package/dist/persistence.cjs.map +1 -1
- package/dist/persistence.d.cts +1 -1
- package/dist/persistence.d.ts +1 -1
- package/dist/persistence.js +14 -14
- package/dist/persistence.js.map +1 -1
- package/dist/project.cjs +5 -5
- package/dist/project.js +3 -3
- package/dist/registry-BA3SLAN7.cjs +46 -0
- package/dist/{registry-N3GTKYCI.cjs.map → registry-BA3SLAN7.cjs.map} +1 -1
- package/dist/registry-KMI5RA22.js +9 -0
- package/dist/{registry-SOXE4RY6.js.map → registry-KMI5RA22.js.map} +1 -1
- package/dist/retry.cjs +4 -4
- package/dist/retry.js +3 -3
- package/dist/{run-until-YZ2ODWOF.js → run-until-BK56WG4P.js} +3 -3
- package/dist/{run-until-YZ2ODWOF.js.map → run-until-BK56WG4P.js.map} +1 -1
- package/dist/{run-until-ZZ2JLPJM.cjs → run-until-L5KQD3PV.cjs} +4 -4
- package/dist/{run-until-ZZ2JLPJM.cjs.map → run-until-L5KQD3PV.cjs.map} +1 -1
- package/dist/{run-CwEJtLPE.d.ts → run-vJHg2eGw.d.cts} +201 -174
- package/dist/{run-CwEJtLPE.d.cts → run-vJHg2eGw.d.ts} +201 -174
- package/dist/sandbox/bwrap.d.cts +20 -20
- package/dist/sandbox/bwrap.d.ts +20 -20
- package/dist/sandbox/index.cjs +39 -39
- package/dist/sandbox/index.cjs.map +1 -1
- package/dist/sandbox/index.d.cts +1 -1
- package/dist/sandbox/index.d.ts +1 -1
- package/dist/sandbox/index.js +35 -35
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/linux-sandbox.d.cts +26 -21
- package/dist/sandbox/linux-sandbox.d.ts +26 -21
- package/dist/server/auth/index.cjs.map +1 -1
- package/dist/server/auth/index.d.cts +6 -6
- package/dist/server/auth/index.d.ts +6 -6
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/auth/orchestrator.d.cts +2 -2
- package/dist/server/auth/orchestrator.d.ts +2 -2
- package/dist/server/auth/types.d.cts +4 -4
- package/dist/server/auth/types.d.ts +4 -4
- package/dist/server/errors-envelope.cjs +14 -14
- package/dist/server/errors-envelope.d.cts +3 -3
- package/dist/server/errors-envelope.d.ts +3 -3
- package/dist/server/errors-envelope.js +3 -3
- package/dist/session-transcript-DFGX6WNL.cjs +50 -0
- package/dist/{session-transcript-EAKR4D2W.cjs.map → session-transcript-DFGX6WNL.cjs.map} +1 -1
- package/dist/session-transcript-MMPWCESR.js +5 -0
- package/dist/{session-transcript-6WKLYIN4.js.map → session-transcript-MMPWCESR.js.map} +1 -1
- package/dist/skills.cjs +7 -7
- package/dist/skills.js +5 -5
- package/dist/stream-object-AFERZU63.js +7 -0
- package/dist/{stream-object-OFM66VUT.js.map → stream-object-AFERZU63.js.map} +1 -1
- package/dist/stream-object-YPXZ4F5P.cjs +20 -0
- package/dist/{stream-object-Y7YCX3BI.cjs.map → stream-object-YPXZ4F5P.cjs.map} +1 -1
- package/dist/subagents-loader.cjs +6 -6
- package/dist/subagents-loader.d.cts +2 -2
- package/dist/subagents-loader.d.ts +2 -2
- package/dist/subagents-loader.js +4 -4
- package/dist/subscription/index.cjs +3 -3
- package/dist/subscription/index.js +2 -2
- package/dist/task-store.cjs +7 -7
- package/dist/task-store.js +4 -4
- package/dist/types/agent-prims.d.ts +4 -4
- package/dist/types/goal-events.d.ts +24 -23
- package/dist/types/run.d.ts +27 -0
- package/dist/types/session-record.d.ts +14 -13
- package/dist/workflow.cjs +27 -27
- package/dist/workflow.js +6 -6
- package/package.json +12 -13
- package/dist/agent-DW2J37IH.js +0 -42
- package/dist/agent-IE6IE4NW.cjs +0 -51
- package/dist/chunk-2ONFGMBV.js.map +0 -1
- package/dist/chunk-56OXYUZE.js.map +0 -1
- package/dist/chunk-56ZM6P7Z.cjs.map +0 -1
- package/dist/chunk-5SB4V74H.js.map +0 -1
- package/dist/chunk-77WHPVXE.js.map +0 -1
- package/dist/chunk-ALU3KC4G.cjs.map +0 -1
- package/dist/chunk-EEZ7CFMM.js.map +0 -1
- package/dist/chunk-GL7CFBVC.js.map +0 -1
- package/dist/chunk-H5YVJB4K.cjs.map +0 -1
- package/dist/chunk-ICYBIC6N.cjs.map +0 -1
- package/dist/chunk-IT3PWBW4.cjs.map +0 -1
- package/dist/chunk-ITLS5LRF.cjs +0 -105
- package/dist/chunk-ITLS5LRF.cjs.map +0 -1
- package/dist/chunk-LTCOPATU.cjs.map +0 -1
- package/dist/chunk-MXSMAGDU.js.map +0 -1
- package/dist/chunk-P34DHHYQ.cjs.map +0 -1
- package/dist/chunk-TAPQWDW2.js.map +0 -1
- package/dist/chunk-THEYGCYS.cjs.map +0 -1
- package/dist/chunk-VNNK6AKU.js.map +0 -1
- package/dist/chunk-VYJSHGAY.js.map +0 -1
- package/dist/chunk-W52DKZBR.cjs.map +0 -1
- package/dist/chunk-XAEAMI5H.js.map +0 -1
- package/dist/chunk-YPQF26X7.cjs.map +0 -1
- package/dist/chunk-YVTNFKZO.js +0 -103
- package/dist/chunk-YVTNFKZO.js.map +0 -1
- package/dist/compact-session-H5TKLD55.cjs +0 -54
- package/dist/compact-session-WNKKIIDW.js +0 -17
- package/dist/context-VAYLPT4O.js +0 -5
- package/dist/context-ZV33V4DC.cjs +0 -22
- package/dist/fs-session-store-6MY7PNI2.js +0 -10
- package/dist/fs-session-store-7E7UPLF2.cjs +0 -19
- package/dist/generate-object-5SCEH2EE.cjs +0 -20
- package/dist/generate-object-I7UD4NWN.js +0 -7
- package/dist/index-manager-KKAK7NT3.js +0 -10
- package/dist/index-manager-R6ZJFEZF.cjs +0 -19
- package/dist/judge-call-SP3G3X2Y.cjs +0 -22
- package/dist/judge-call-TAEIXMYG.js +0 -5
- package/dist/registry-N3GTKYCI.cjs +0 -46
- package/dist/registry-SOXE4RY6.js +0 -9
- package/dist/session-transcript-6WKLYIN4.js +0 -5
- package/dist/session-transcript-EAKR4D2W.cjs +0 -50
- package/dist/stream-object-OFM66VUT.js +0 -7
- package/dist/stream-object-Y7YCX3BI.cjs +0 -20
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/session/agent-session-store.ts","../src/internal/session/agent-session.ts"],"names":["SessionTranscript","reconstructMessages","sessions","transcriptKey","hydratedKeys","diag"],"mappings":";;;;;;;AA8BA,SAAS,cAAA,CACP,OACA,IAAA,EACmB;AACnB,EAAA,OAAOA,mCAAA,CAAkB,WAAA,CAAY,KAAA,EAAO,IAAI,CAAA;AAClD;AAyBA,SAAS,aAAa,KAAA,EAAgD;AACpE,EAAA,MAAM,YAA2B,EAAC;AAClC,EAAA,MAAM,cAAyC,EAAC;AAChD,EAAA,MAAM,YAAqD,EAAC;AAC5D,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,IAAI,KAAK,IAAA,KAAS,iBAAA,EAAmB,SAAA,CAAU,QAAA,GAAW,KAAK,OAAA,CAAQ,IAAA;AAAA,SAAA,IAC9D,KAAK,IAAA,KAAS,kBAAA,EAAoB,SAAA,CAAU,IAAA,GAAO,KAAK,OAAA,CAAQ,IAAA;AAAA,SAAA,IAChE,KAAK,IAAA,KAAS,UAAA;AACrB,MAAA,SAAA,CAAU,IAAA,CAAK;AAAA,QACb,EAAA,EAAI,KAAK,OAAA,CAAQ,MAAA;AAAA,QACjB,IAAA,EAAM,KAAK,OAAA,CAAQ,IAAA;AAAA,QACnB,KAAA,EAAQ,IAAA,CAAK,OAAA,CAAQ,IAAA,IAAgD;AAAC,OACvE,CAAA;AAAA;AAED,MAAA,WAAA,CAAY,IAAA,CAAK;AAAA,QACf,SAAA,EAAW,KAAK,OAAA,CAAQ,MAAA;AAAA,QACxB,OAAA,EAAS,KAAK,OAAA,CAAQ,MAAA;AAAA,QACtB,OAAA,EAAS,KAAK,OAAA,CAAQ;AAAA,OACvB,CAAA;AAAA,EACL;AACA,EAAA,IAAI,SAAA,CAAU,MAAA,GAAS,CAAA,EAAG,SAAA,CAAU,SAAA,GAAY,SAAA;AAChD,EAAA,OAAO,EAAE,WAAW,WAAA,EAAY;AAClC;AAGA,SAAS,oBAAoB,CAAA,EAA2B;AACtD,EAAA,OAAO,CAAA,CAAE,SAAS,MAAA,IAAa,CAAA,CAAE,aAAa,MAAA,IAAA,CAAc,CAAA,CAAE,SAAA,EAAW,MAAA,IAAU,CAAA,IAAK,CAAA;AAC1F;AAQA,SAAS,kBAAA,CACP,YACA,YAAA,EACM;AACN,EAAA,KAAA,MAAW,MAAM,YAAA,EAAc;AAC7B,IAAA,IAAI,EAAA,CAAG,SAAS,uBAAA,EAAyB;AACzC,IAAA,MAAM,EAAE,SAAA,EAAW,WAAA,KAAgB,YAAA,CAAa,EAAA,CAAG,KAAK,KAAK,CAAA;AAC7D,IAAA,IAAI,mBAAA,CAAoB,SAAS,CAAA,EAAG,UAAA,CAAW,oBAAoB,SAAS,CAAA;AAC5E,IAAA,IAAI,WAAA,CAAY,MAAA,GAAS,CAAA,EAAG,UAAA,CAAW,kBAAkB,WAAW,CAAA;AAAA,EACtE;AACF;AAmBA,eAAsB,mBAAA,CACpB,OACA,OAAA,EAC2B;AAC3B,EAAA,MAAM,OAAA,GAAU,MAAM,KAAA,CAAM,WAAA,CAAY,OAAO,CAAA;AAC/C,EAAA,OAAOC,qCAAA,CAAoB,OAAO,CAAA,CAAE,GAAA,CAAI,sBAAsB,CAAA;AAChE;AAKA,SAAS,WAAW,CAAA,EAAuB;AACzC,EAAA,IAAI,CAAA,CAAE,IAAA,KAAS,MAAA,EAAQ,OAAO,EAAE,IAAA,IAAQ,EAAA;AACxC,EAAA,IAAI,EAAE,IAAA,KAAS,UAAA,SAAmB,CAAA,YAAA,EAAe,CAAA,CAAE,QAAQ,EAAE,CAAA,CAAA;AAC7D,EAAA,IAAI,CAAA,CAAE,SAAS,aAAA,EAAe;AAC5B,IAAA,MAAM,IAAA,GAAO,OAAO,CAAA,CAAE,OAAA,KAAY,QAAA,GAAW,EAAE,OAAA,GAAU,IAAA,CAAK,SAAA,CAAU,CAAA,CAAE,OAAO,CAAA;AACjF,IAAA,OAAO,iBAAiB,IAAI,CAAA,CAAA;AAAA,EAC9B;AACA,EAAA,OAAO,EAAA;AACT;AAGA,SAAS,uBAAuB,CAAA,EAGb;AACjB,EAAA,MAAM,IAAA,GAAO,CAAA,CAAE,IAAA,KAAS,MAAA,GAAS,MAAA,GAAS,WAAA;AAC1C,EAAA,MAAM,IAAA,GAAO,CAAA,CAAE,OAAA,CACZ,GAAA,CAAI,UAAU,CAAA,CACd,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,GAAS,CAAC,CAAA,CAC1B,KAAK,IAAI,CAAA;AACZ,EAAA,OAAO,EAAE,MAAM,IAAA,EAAK;AACtB;AAGA,SAAS,YAAA,CACP,YACA,WAAA,EAC0B;AAC1B,EAAA,OAAO,UAAA,CAAW,OAAA,EAAQ,CAAE,KAAA,CAAM,WAAW,CAAA;AAC/C;AAUA,eAAsB,WAAA,CACpB,KAAA,EACA,GAAA,EACA,SAAA,EACA,IAAA,EACe;AACf,EAAA,MAAM,KAAA,GAAQ,MAAM,KAAA,CAAM,WAAA,CAAY,IAAI,OAAO,CAAA;AACjD,EAAA,MAAM,UAAA,GAAa,cAAA,CAAe,KAAA,EAAO,EAAE,GAAA,EAAK,GAAA,CAAI,GAAA,EAAK,SAAA,EAAW,KAAA,EAAO,GAAA,CAAI,KAAA,EAAO,CAAA;AACtF,EAAA,UAAA,CAAW,cAAA,CAAe,KAAK,QAAQ,CAAA;AACvC,EAAA,kBAAA,CAAmB,UAAA,EAAY,KAAK,YAAY,CAAA;AAChD,EAAA,MAAM,KAAA,CAAM,cAAc,GAAA,CAAI,OAAA,EAAS,aAAa,UAAA,EAAY,KAAA,CAAM,MAAM,CAAC,CAAA;AAC/E;;;ACjJA,IAAM,aAAA,uBAAoB,GAAA,EAA2B;AACrD,IAAM,YAAA,uBAAmB,GAAA,EAAoB;AAStC,SAAS,oBAAA,CAAqB,SAAiB,OAAA,EAA+B;AACnF,EAAA,MAAM,QAAA,GAAWC,0BAAA,CAAS,GAAA,CAAI,OAAO,KAAK,EAAC;AAC3C,EAAA,QAAA,CAAS,KAAK,OAAO,CAAA;AAIrB,EAAAA,0BAAA,CAAS,OAAO,OAAO,CAAA;AACvB,EAAAA,0BAAA,CAAS,GAAA,CAAI,SAAS,QAAQ,CAAA;AAC9B,EAAA,WAAA,EAAY;AACd;AAWO,IAAM,wBAAA,GAA2B,EAAA;AAExC,SAAS,WAAA,GAAoB;AAC3B,EAAA,OAAOA,0BAAA,CAAS,OAAO,wBAAA,EAA0B;AAC/C,IAAA,MAAM,UAAA,GAAaA,0BAAA,CAAS,IAAA,EAAK,CAAE,MAAK,CAAE,KAAA;AAC1C,IAAA,IAAI,eAAe,MAAA,EAAW;AAC9B,IAAAA,0BAAA,CAAS,OAAO,UAAU,CAAA;AAC1B,IAAA,oBAAA,CAAqB,UAAU,CAAA;AAAA,EACjC;AACF;AAeA,SAAS,qBAAqB,OAAA,EAAuB;AACnD,EAAA,MAAM,MAAA,GAASC,gCAAc,EAAA,EAAI,OAAO,EAAE,KAAA,CAAM,CAAA,GAAI,OAAA,CAAQ,MAAA,GAAS,CAAC,CAAA;AACtE,EAAA,KAAA,MAAW,CAAA,IAAK,CAAC,GAAGC,8BAAY,CAAA,EAAG,IAAI,CAAA,CAAE,QAAA,CAAS,MAAM,CAAA,EAAGA,8BAAA,CAAa,MAAA,CAAO,CAAC,CAAA;AAChF,EAAA,KAAA,MAAW,CAAA,IAAK,CAAC,GAAG,aAAA,CAAc,MAAM,CAAA,EAAG,IAAI,CAAA,CAAE,QAAA,CAAS,MAAM,CAAA,EAAG,aAAA,CAAc,OAAO,CAAC,CAAA;AACzF,EAAA,KAAA,MAAW,CAAA,IAAK,CAAC,GAAG,YAAA,CAAa,MAAM,CAAA,EAAG,IAAI,CAAA,CAAE,QAAA,CAAS,MAAM,CAAA,EAAG,YAAA,CAAa,OAAO,CAAC,CAAA;AACzF;AAaO,SAAS,eAAA,CAAgB,KAAa,OAAA,EAAyB;AACpE,EAAA,MAAM,KAAA,GAAQD,+BAAA,CAAc,GAAA,EAAK,OAAO,CAAA;AACxC,EAAA,IAAI,SAAA,GAAY,CAAA;AAUhB,EAAA,IAAIC,8BAAA,CAAa,MAAA,CAAO,KAAK,CAAA,EAAG,SAAA,EAAA;AAChC,EAAA,IAAI,aAAA,CAAc,MAAA,CAAO,KAAK,CAAA,EAAG,SAAA,EAAA;AACjC,EAAA,IAAI,YAAA,CAAa,MAAA,CAAO,KAAK,CAAA,EAAG,SAAA,EAAA;AAChC,EAAA,OAAO,SAAA;AACT;AAEO,SAAS,mBAAmB,OAAA,EAAmC;AACpE,EAAA,OAAOF,0BAAA,CAAS,GAAA,CAAI,OAAO,CAAA,IAAK,EAAC;AACnC;AAWO,SAAS,uBAAA,CACd,KAAA,EACA,GAAA,EACA,SAAA,EACA,MACA,SAAA,EACM;AACN,EAAA,MAAM,GAAA,GAAMC,+BAAA,CAAc,GAAA,CAAI,GAAA,EAAK,IAAI,OAAO,CAAA;AAM9C,EAAA,MAAM,OAAA,GAAA,CAAW,cAAc,GAAA,CAAI,GAAG,KAAK,OAAA,CAAQ,OAAA,EAAQ,EAAG,IAAA,CAAK,YAAY;AAC7E,IAAA,IAAI;AACF,MAAA,MAAM,WAAA,CAAY,KAAA,EAAO,GAAA,EAAK,SAAA,EAAW,IAAI,CAAA;AAC7C,MAAA,MAAM,KAAA,GAAA,CAAS,YAAA,CAAa,GAAA,CAAI,GAAG,KAAK,CAAA,IAAK,CAAA;AAC7C,MAAA,YAAA,CAAa,GAAA,CAAI,KAAK,KAAK,CAAA;AAI3B,MAAA,IAAI,IAAA,CAAK,gBAAgB,KAAA,CAAA,EAAW;AAClC,QAAA,MAAM,EAAE,mBAAA,EAAoB,GAAI,MAAM,OAAO,gCAAsB,CAAA;AACnE,QAAA,MAAM,KAAA,GAAQ,MAAM,mBAAA,CAAoB;AAAA,UACtC,KAAA;AAAA,UACA,GAAA;AAAA,UACA,SAAA;AAAA,UACA,UAAA,EAAY,KAAK,WAAA,CAAY,UAAA;AAAA,UAC7B,aAAA,EAAe,KAAK,WAAA,CAAY,aAAA;AAAA,UAChC,SAAA,EAAW,KAAA;AAAA,UACX,SAAA,EAAW,KAAK,WAAA,CAAY;AAAA,SAC7B,CAAA;AACD,QAAA,IAAI,OAAO,SAAA,IAAY;AAAA,MACzB;AAAA,IACF,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,MAAM,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AACjE,MAAAE,sBAAA,CAAK,CAAA,+CAAA,EAAkD,GAAA,CAAI,OAAO,CAAA,GAAA,EAAM,GAAG;AAAA,CAAI,CAAA;AAAA,IACjF;AAAA,EACF,CAAC,CAAA;AACD,EAAA,aAAA,CAAc,GAAA;AAAA,IACZ,GAAA;AAAA,IACA,OAAA,CAAQ,IAAA;AAAA,MACN,MAAM,MAAA;AAAA,MACN,MAAM;AAAA;AACR,GACF;AACF;AAQA,eAAsB,cAAA,CACpB,SACA,GAAA,EACe;AACf,EAAA,MAAM,GAAA,GAAMF,+BAAA,CAAc,GAAA,CAAI,GAAA,EAAK,OAAO,CAAA;AAC1C,EAAA,IAAIC,8BAAA,CAAa,GAAA,CAAI,GAAG,CAAA,EAAG;AAC3B,EAAAA,8BAAA,CAAa,IAAI,GAAG,CAAA;AAEpB,EAAA,MAAM,SAAA,GAAY,MAAM,mBAAA,CAAoB,GAAA,CAAI,OAAO,OAAO,CAAA;AAC9D,EAAA,IAAI,SAAA,CAAU,WAAW,CAAA,EAAG;AAM5B,EAAAF,0BAAA,CAAS,GAAA,CAAI,SAAS,SAAS,CAAA;AACjC;AAQA,eAAsB,kBAAA,GAAoC;AACxD,EAAA,OAAO,aAAA,CAAc,OAAO,CAAA,EAAG;AAC7B,IAAA,MAAM,GAAA,GAAM,KAAA,CAAM,IAAA,CAAK,aAAA,CAAc,QAAQ,CAAA;AAC7C,IAAA,aAAA,CAAc,KAAA,EAAM;AACpB,IAAA,MAAM,OAAA,CAAQ,IAAI,GAAG,CAAA;AAAA,EACvB;AACF;AAEO,SAAS,aAAa,OAAA,EAAuB;AAClD,EAAAA,0BAAA,CAAS,OAAO,OAAO,CAAA;AACzB;AAYO,SAAS,mBAAA,CACd,GAAA,EACA,OAAA,EACA,EAAA,EACY;AACZ,EAAA,MAAM,GAAA,GAAMC,+BAAA,CAAc,GAAA,EAAK,OAAO,CAAA;AACtC,EAAA,MAAM,QAAQ,aAAA,CAAc,GAAA,CAAI,GAAG,CAAA,IAAK,QAAQ,OAAA,EAAQ;AACxD,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,IAAA,CAAK,EAAE,CAAA;AAC5B,EAAA,aAAA,CAAc,GAAA;AAAA,IACZ,GAAA;AAAA,IACA,MAAA,CAAO,IAAA;AAAA,MACL,MAAM,MAAA;AAAA,MACN,MAAM;AAAA;AACR,GACF;AACA,EAAA,OAAO,MAAA;AACT","file":"chunk-H5YVJB4K.cjs","sourcesContent":["/**\n * SE40 — native session store over the Claude-shaped `.jsonl` transcript.\n *\n * The theokit session format IS the Claude Code record shape (see\n * `../../persistence/session-transcript.ts`). This module owns the FS lifecycle:\n * read existing records, seed a {@link SessionTranscript}'s parent chain, append\n * the new turn (user + assistant/tool blocks), and write append-only. Compaction\n * appends a `compact_boundary` record (a new root) — it NEVER shrinks the line\n * set (that would break the DAG). Reads reconstruct `LlmMessage[]` via\n * {@link reconstructMessages}, narrowed to the in-memory {@link SessionMessage}.\n *\n * @internal\n */\n\nimport type { ConversationStep, ConversationTurn } from \"../../types/conversation.js\";\nimport type { SessionStore } from \"../../types/session-store.js\";\nimport {\n type AssistantTurn,\n reconstructMessages,\n type SessionRecord,\n SessionTranscript,\n} from \"../persistence/session-transcript.js\";\nimport type { SessionMessage } from \"./session-types.js\";\n\n/**\n * Seed a {@link SessionTranscript} from the already-on-disk records so a new\n * append parents correctly on the existing DAG leaf. The transcript re-emits the\n * seeded records verbatim before the new turn, so the whole file rewrites atomically\n * as one append-only line set. Returns the transcript primed with the prior records.\n */\nfunction seedTranscript(\n prior: readonly SessionRecord[],\n opts: { cwd: string; sessionId: string; model: string },\n): SessionTranscript {\n return SessionTranscript.fromRecords(prior, opts);\n}\n\n/** The turn to persist: the user's text plus the rich per-turn conversation view. */\nexport interface PersistTurnInput {\n userText: string;\n conversation: readonly ConversationTurn[];\n /**\n * M50 — when supplied, size-driven auto-compaction runs in the SAME write chain after the turn\n * persists (usage real vs the model's context window; summarizer injected by the caller).\n */\n autoCompact?: {\n usageTotal?: number | undefined;\n contextWindow?: number | undefined;\n summarize: (\n messages: readonly import(\"../../compaction.js\").CompressibleMessage[],\n ) => Promise<string>;\n };\n}\n\ninterface MappedTurn {\n assistant: AssistantTurn;\n toolResults: Array<{ toolUseId: string; content: string; isError?: boolean }>;\n}\n\n/** Fold one agent turn's steps into an assistant record + paired tool results. */\nfunction mapAgentTurn(steps: readonly ConversationStep[]): MappedTurn {\n const assistant: AssistantTurn = {};\n const toolResults: MappedTurn[\"toolResults\"] = [];\n const toolCalls: NonNullable<AssistantTurn[\"toolCalls\"]> = [];\n for (const step of steps) {\n if (step.type === \"thinkingMessage\") assistant.thinking = step.message.text;\n else if (step.type === \"assistantMessage\") assistant.text = step.message.text;\n else if (step.type === \"toolCall\")\n toolCalls.push({\n id: step.message.callId,\n name: step.message.name,\n input: (step.message.args as Record<string, unknown> | undefined) ?? {},\n });\n else\n toolResults.push({\n toolUseId: step.message.callId,\n content: step.message.result,\n isError: step.message.isError,\n });\n }\n if (toolCalls.length > 0) assistant.toolCalls = toolCalls;\n return { assistant, toolResults };\n}\n\n/** True when the assistant record carries any content worth emitting. */\nfunction hasAssistantContent(a: AssistantTurn): boolean {\n return a.text !== undefined || a.thinking !== undefined || (a.toolCalls?.length ?? 0) > 0;\n}\n\n/**\n * Map the rich `ConversationTurn[]` (from `run.conversation()`) into the native\n * assistant/tool records. One transcript turn is: an `assistant` record carrying\n * thinking + text + tool_use blocks, then (when present) a `user` record carrying\n * the paired tool_result blocks. Tool results pair with tool_use by `callId`.\n */\nfunction appendConversation(\n transcript: SessionTranscript,\n conversation: readonly ConversationTurn[],\n): void {\n for (const ct of conversation) {\n if (ct.type !== \"agentConversationTurn\") continue;\n const { assistant, toolResults } = mapAgentTurn(ct.turn.steps);\n if (hasAssistantContent(assistant)) transcript.appendAssistantTurn(assistant);\n if (toolResults.length > 0) transcript.appendToolResults(toolResults);\n }\n}\n\n/**\n * The per-agent transcript metadata used to seed a {@link SessionTranscript}. The\n * actual record I/O goes through the injected {@link SessionStore} (SE41), so this\n * no longer carries a `baseDir` — the store is already bound to its location.\n */\nexport interface TranscriptLocation {\n cwd: string;\n agentId: string;\n model: string;\n}\n\n/**\n * Read the session's records via the {@link SessionStore} and reconstruct the\n * resumable history, narrowed to the in-memory {@link SessionMessage} shape\n * (user/assistant text). Tool turns fold into assistant-role context so resume\n * keeps tool history. Works identically for the FS default and an external store.\n */\nexport async function readSessionMessages(\n store: SessionStore,\n agentId: string,\n): Promise<SessionMessage[]> {\n const records = await store.readRecords(agentId);\n return reconstructMessages(records).map(narrowToSessionMessage);\n}\n\ntype NarrowPart = { type: string; text?: string; content?: unknown; name?: string };\n\n/** Render one reconstructed content part as folded session text. */\nfunction partToText(p: NarrowPart): string {\n if (p.type === \"text\") return p.text ?? \"\";\n if (p.type === \"tool_use\") return `[tool call] ${p.name ?? \"\"}`;\n if (p.type === \"tool_result\") {\n const body = typeof p.content === \"string\" ? p.content : JSON.stringify(p.content);\n return `[tool result] ${body}`;\n }\n return \"\";\n}\n\n/** Flatten a reconstructed `LlmMessage` to the narrowed in-memory `SessionMessage`. */\nfunction narrowToSessionMessage(m: {\n role: \"system\" | \"user\" | \"assistant\";\n content: NarrowPart[];\n}): SessionMessage {\n const role = m.role === \"user\" ? \"user\" : \"assistant\";\n const text = m.content\n .map(partToText)\n .filter((s) => s.length > 0)\n .join(\"\\n\");\n return { role, text };\n}\n\n/** The records appended by the last turn (the delta beyond the seeded prior). */\nfunction deltaRecords(\n transcript: SessionTranscript,\n priorLength: number,\n): readonly SessionRecord[] {\n return transcript.records().slice(priorLength);\n}\n\n/**\n * Persist one conversation turn (user + assistant/tool records) to the session\n * store, append-only. Reads the prior records to seed the parent chain, folds the\n * new turn, and appends ONLY the delta via {@link SessionStore.appendRecords}\n * (the store owns the append atomicity — the FS default serializes under a file\n * lock). Within one process, turns for an agent are already chained upstream, so\n * the read→append is ordered; cross-host ordering is the external store's contract.\n */\nexport async function persistTurn(\n store: SessionStore,\n loc: TranscriptLocation,\n sessionId: string,\n turn: PersistTurnInput,\n): Promise<void> {\n const prior = await store.readRecords(loc.agentId);\n const transcript = seedTranscript(prior, { cwd: loc.cwd, sessionId, model: loc.model });\n transcript.appendUserTurn(turn.userText);\n appendConversation(transcript, turn.conversation);\n await store.appendRecords(loc.agentId, deltaRecords(transcript, prior.length));\n}\n\n/**\n * Append-only compaction: add a `compact_boundary` system record (a new root) so\n * resume replays only the post-boundary continuation. Appends only the boundary\n * record via the store — NEVER rewrites or shrinks the prior records.\n */\nexport async function appendCompactBoundaryRecord(\n store: SessionStore,\n loc: TranscriptLocation,\n sessionId: string,\n meta: { preTokens: number; trigger: string },\n): Promise<void> {\n const prior = await store.readRecords(loc.agentId);\n const transcript = seedTranscript(prior, { cwd: loc.cwd, sessionId, model: loc.model });\n transcript.appendCompactBoundary(meta);\n await store.appendRecords(loc.agentId, deltaRecords(transcript, prior.length));\n}\n","import type { SessionStore } from \"../../types/session-store.js\";\nimport {\n type PersistTurnInput,\n persistTurn,\n readSessionMessages,\n type TranscriptLocation,\n} from \"./agent-session-store.js\";\n\n/**\n * Per-agent conversation history kept across runs (and across `Agent.resume()`\n * within the same process). Lets the fixture responder recall prior facts when\n * the user asks a follow-up question.\n *\n * SE40 — disk persistence IS the native Claude-shaped `.jsonl` transcript. The\n * in-memory cache holds the narrowed `SessionMessage[]` (user/assistant text) so\n * a send can read `priorMessages` synchronously; the whole rich turn (user +\n * assistant + tool blocks) lands on disk once per send via {@link persistTurnToTranscript}\n * (fed by `run.conversation()`). Hydration on resume reconstructs the session by\n * walking the transcript DAG (`readSessionMessages`).\n *\n * @internal\n */\n\n// `SessionMessage` lives in `./session-types.ts` (leaf types file). Re-exported\n// for back-compat with downstream importers that pulled it from here.\nexport type { SessionMessage } from \"./session-types.js\";\n\nimport type { SessionMessage } from \"./session-types.js\";\n\n// M75 — o cache mora numa folha; ver session-cache.ts para a razao (quebra de ciclo por extracao).\nexport {\n hydratedKeys,\n invalidateSessionCache,\n sessions,\n transcriptKey,\n} from \"./session-cache.js\";\n\nimport { diag } from \"../diagnostics.js\";\nimport { hydratedKeys, sessions, transcriptKey } from \"./session-cache.js\";\n\nconst pendingWrites = new Map<string, Promise<void>>();\nconst recordCounts = new Map<string, number>();\n\n/**\n * Append a session message to the in-memory cache only. Disk persistence for the\n * whole turn happens once per send via {@link persistTurnToTranscript}; the cache\n * feeds `priorMessages` / `onBeforeSend.previousMessageCount` synchronously.\n *\n * @internal\n */\nexport function appendSessionMessage(agentId: string, message: SessionMessage): void {\n const existing = sessions.get(agentId) ?? [];\n existing.push(message);\n // `delete` + `set` reinsere no FIM: `Map` do JS preserva ordem de inserção, então a primeira\n // chave é sempre a menos recentemente tocada. É o LRU inteiro, sem estrutura nova (rung 2/5 da\n // parcimônia — a ordenação que precisamos já é garantia da linguagem).\n sessions.delete(agentId);\n sessions.set(agentId, existing);\n aplicarTeto();\n}\n\n/**\n * Teto de sessões mantidas em memória.\n *\n * O runtime só lê a sessão **ativa**; o resto é cache puro, reconstruível do transcript em disco.\n * 32 é folgado de propósito — o caminho primário de remoção é o `descartarSessao()` explícito no\n * fim da vida do agente, e este teto é rede de segurança contra um processo de vida longa que roda\n * centenas de sessões (risco #2 do plano: um teto apertado poderia evictar sessão ainda referenciada\n * por um fluxo assíncrono em voo).\n */\nexport const TETO_DE_SESSOES_EM_CACHE = 32;\n\nfunction aplicarTeto(): void {\n while (sessions.size > TETO_DE_SESSOES_EM_CACHE) {\n const maisAntiga = sessions.keys().next().value;\n if (maisAntiga === undefined) return;\n sessions.delete(maisAntiga);\n esquecerEscrituracao(maisAntiga);\n }\n}\n\n/**\n * Apaga a escrituração de um `agentId` em TODOS os `cwd` onde ele aparece.\n *\n * Os três mapas são chaveados por `transcriptKey(cwd, agentId)`; `sessions` é o único chaveado pelo\n * `agentId` cru. A primeira versão do teto apagava os três com a chave de `sessions` — isto é, não\n * apagava nada — e deixava `hydratedKeys` **órfã**. Como `hydrateSession` retorna cedo quando a\n * marca está lá, uma sessão evictada voltava **vazia** em vez de reidratar do disco: amnésia\n * silenciosa, e regressão nova do M95, porque antes dele nada evictava.\n *\n * O teto só conhece o `agentId`, não o `cwd`, então varre pelo sufixo — que é o formato que\n * `transcriptKey` produz. Varredura em vez de índice porque estes mapas têm dezenas de entradas,\n * não milhares: um índice inverso aqui seria estrutura nova para um problema que não existe.\n */\nfunction esquecerEscrituracao(agentId: string): void {\n const sufixo = transcriptKey(\"\", agentId).slice(0 - agentId.length - 2);\n for (const k of [...hydratedKeys]) if (k.endsWith(sufixo)) hydratedKeys.delete(k);\n for (const k of [...pendingWrites.keys()]) if (k.endsWith(sufixo)) pendingWrites.delete(k);\n for (const k of [...recordCounts.keys()]) if (k.endsWith(sufixo)) recordCounts.delete(k);\n}\n\n/**\n * Apaga a escrituração de módulo do agente e devolve quantas entradas saíram.\n *\n * M95 — `invalidateSessionCache` limpava **dois** dos quatro mapas (`sessions`, `hydratedKeys`);\n * `pendingWrites` e `recordCounts` nunca eram tocados por id, então cresciam pela vida do processo.\n * Nenhum dos dois é grande por entrada — o vazamento é de contagem, não de volume — mas cache sem\n * dono da remoção é cache que só cresce.\n *\n * Devolve a contagem para que o chamador possa provar a remoção; um segundo descarte devolve 0,\n * que é o que torna o teste de idempotência possível sem expor os mapas.\n */\nexport function descartarSessao(cwd: string, agentId: string): number {\n const chave = transcriptKey(cwd, agentId);\n let removidas = 0;\n // `sessions` NÃO é apagado aqui — e a distinção é medida, não estética. Ele é a conversa\n // legível, e há leitor legítimo DEPOIS do dispose: o golden `two-concurrent-sends-serialize`\n // chama `getSessionMessages(agentId)` após `agent.dispose()`. Apagá-lo aqui devolvia lista vazia\n // e quebrava dois goldens. Quem o limita é o teto LRU acima; os três abaixo são escrituração\n // pura, sem leitor pós-dispose.\n //\n // A chave é `transcriptKey(cwd, agentId)` nos TRÊS — não o `agentId` cru. A primeira versão\n // apagava dois deles pelo `agentId` e portanto **nunca apagava nada**; o teste não pegava porque\n // afirmava apenas que a SEGUNDA chamada devolve 0, o que é verdade mesmo assim.\n if (hydratedKeys.delete(chave)) removidas++;\n if (pendingWrites.delete(chave)) removidas++;\n if (recordCounts.delete(chave)) removidas++;\n return removidas;\n}\n\nexport function getSessionMessages(agentId: string): SessionMessage[] {\n return sessions.get(agentId) ?? [];\n}\n\n/**\n * Persist a full conversation turn (user + assistant + tool blocks) to the native\n * transcript. Chained per-(agent, transcript) so on-disk order matches send order,\n * and fire-and-forget so `send()` is not blocked by disk I/O. Every\n * M50 — when the caller supplies `turn.autoCompact`, size-driven auto-compaction (usage real vs\n * the model's context window) runs in this same chain, surfaced via the optional `onCompact` observer.\n *\n * @internal\n */\nexport function persistTurnToTranscript(\n store: SessionStore,\n loc: TranscriptLocation,\n sessionId: string,\n turn: PersistTurnInput,\n onCompact?: () => void,\n): void {\n const key = transcriptKey(loc.cwd, loc.agentId);\n // Divida PRE-EXISTENTE, exposta quando o M75 consertou a config Biome que abortava antes\n // de varrer estes arquivos (raiz aninhada em refactor/). Nao e codigo novo e nao foi tocado\n // pelo M75; refatorar internals do SDK sem revisao trocaria um problema visivel por um diff\n // arriscado. Rastreado em usetheodev/theokit-sdk#151.\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: ver a razao logo acima\n const chained = (pendingWrites.get(key) ?? Promise.resolve()).then(async () => {\n try {\n await persistTurn(store, loc, sessionId, turn);\n const count = (recordCounts.get(key) ?? 0) + 1;\n recordCounts.set(key, count);\n // M50 — the 50-turn no-summary boundary stub is GONE (it silently amnesia'd the session).\n // Auto-compaction is now size-driven with a real summary: see `maybeAutoCompact` below,\n // invoked in this same write chain by the post-run lifecycle when usage is known.\n if (turn.autoCompact !== undefined) {\n const { autoCompactIfNeeded } = await import(\"./compact-session.js\");\n const fired = await autoCompactIfNeeded({\n store,\n loc,\n sessionId,\n usageTotal: turn.autoCompact.usageTotal,\n contextWindow: turn.autoCompact.contextWindow,\n turnCount: count,\n summarize: turn.autoCompact.summarize,\n });\n if (fired) onCompact?.();\n }\n } catch (cause) {\n const msg = cause instanceof Error ? cause.message : String(cause);\n diag(`[theokit-sdk] session transcript write failed (${loc.agentId}): ${msg}\\n`);\n }\n });\n pendingWrites.set(\n key,\n chained.then(\n () => undefined,\n () => undefined,\n ),\n );\n}\n\n/**\n * Load the persisted transcript into the in-memory cache. Idempotent per\n * (baseDir, cwd, agentId). Call once per agent lifecycle before the first read.\n *\n * @internal\n */\nexport async function hydrateSession(\n agentId: string,\n loc: { store: SessionStore; cwd: string },\n): Promise<void> {\n const key = transcriptKey(loc.cwd, agentId);\n if (hydratedKeys.has(key)) return;\n hydratedKeys.add(key);\n\n const persisted = await readSessionMessages(loc.store, agentId);\n if (persisted.length === 0) return;\n // M51 review F4 — the DISK is the source of truth at hydration time: after an invalidation\n // (compact/inject), an in-flight turn may have repopulated the cache with a SINGLE message before\n // this hydrate ran; the old \"skip when non-empty\" guard then pinned the parent to a 1-message\n // context (history + injected pair lost until restart). The persist chain serializes writes, so\n // the disk already contains that in-flight turn — replacing is always correct.\n sessions.set(agentId, persisted);\n}\n\n/**\n * Wait for all pending transcript writes to settle. Used by tests and by the\n * agent dispose path so on-disk state matches in-memory before the caller proceeds.\n *\n * @internal\n */\nexport async function flushSessionWrites(): Promise<void> {\n while (pendingWrites.size > 0) {\n const all = Array.from(pendingWrites.values());\n pendingWrites.clear();\n await Promise.all(all);\n }\n}\n\nexport function clearSession(agentId: string): void {\n sessions.delete(agentId);\n}\n\n/**\n * M50 review F1 — drop BOTH the message cache and the hydration marker so the NEXT send re-hydrates\n * from disk. Without this, a compaction only helps after a process restart: the live process keeps\n * sending the full pre-compact history (the cache is read synchronously by every send).\n */\n/**\n * M50 review F5 — run `fn` serialized on the SAME per-(cwd,agentId) write chain the per-turn\n * persistence uses, so a manual `Agent.compact` can never interleave with an in-flight turn's\n * writes (boundary landing mid-turn would orphan the turn from the replay).\n */\nexport function enqueueSessionWrite<T>(\n cwd: string,\n agentId: string,\n fn: () => Promise<T>,\n): Promise<T> {\n const key = transcriptKey(cwd, agentId);\n const prior = pendingWrites.get(key) ?? Promise.resolve();\n const result = prior.then(fn);\n pendingWrites.set(\n key,\n result.then(\n () => undefined,\n () => undefined,\n ),\n );\n return result;\n}\n\n/** Test-only: drop every cached session and hydration marker. @internal */\nexport function clearAllSessions(): void {\n sessions.clear();\n hydratedKeys.clear();\n recordCounts.clear();\n // M50 review F11 — the auto-compact attempt marks live on globalThis; tests reset them here.\n const g = globalThis as unknown as Record<symbol, Map<string, number>>;\n const sym = Symbol.for(\"theokit-sdk.session.auto-compact-attempts\");\n g[sym]?.clear();\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/persistence/atomic-write.ts"],"names":["statfs","diag","dirname","randomBytes","open","rename","unlink","mkdir"],"mappings":"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","file":"chunk-ICYBIC6N.cjs","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { mkdir, open, rename, statfs, unlink } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { diag } from \"../diagnostics.js\";\n\n// T5.8 — Linux filesystem magic numbers (from `<linux/magic.h>`).\n// Used by `detectNetworkFsName` to identify the parent directory's\n// filesystem type from a `statfs()` return value. The four entries\n// below cover the network/FUSE cases where `rename()` is best-effort\n// rather than strictly atomic; everything else is treated as local.\nconst NETWORK_FS_MAGIC: ReadonlyMap<number, string> = new Map([\n [0x6969, \"nfs\"],\n [0x517b, \"smb\"],\n [0xff534d42, \"cifs\"],\n [0x65735546, \"fuse\"],\n]);\n\n/**\n * T5.8 — Map a `statfs().type` magic number to a network-FS label, or\n * `null` for local filesystems. Pure function — exported via the\n * `__TESTING__` seam so unit tests can drive the parse logic without\n * needing a network mount.\n *\n * @internal\n */\nfunction detectNetworkFsName(typeMagic: number): string | null {\n return NETWORK_FS_MAGIC.get(typeMagic) ?? null;\n}\n\nconst warnedNfsDirs = new Set<string>();\n\n/**\n * T5.8 — Best-effort one-shot stderr warning when `dirPath` lives on a\n * network/FUSE filesystem. Silent no-op on local filesystems, on\n * statfs failure (Windows / Node < 18.15 / EACCES), or after the\n * first warning per (dir + label) pair. Mirrors the `sqlite-wal.ts`\n * warn-once-per-label pattern (D63).\n *\n * @internal\n */\nasync function warnOnNetworkFsOnce(dirPath: string, label: string): Promise<void> {\n const key = `${dirPath}\\0${label}`;\n if (warnedNfsDirs.has(key)) return;\n warnedNfsDirs.add(key);\n try {\n const info = await statfs(dirPath);\n const fsName = detectNetworkFsName(info.type);\n if (fsName === null) return;\n diag(\n `[theokit-sdk] ${label}: detected network fs (${fsName}) at ${dirPath} — ` +\n \"rename() atomicity guarantees may be weaker than expected.\\n\",\n );\n } catch {\n // statfs unavailable (Windows / Node < 18.15) or unreadable —\n // silent fallback. The warning is purely informational.\n }\n}\n\n/**\n * T5.8 — Test seam exposing the pure detection function so unit tests\n * can assert magic-number coverage without spinning up a network FS.\n * NOT included in the public barrel.\n *\n * @internal\n */\nexport function __TESTING__detectNetworkFsName(typeMagic: number): string | null {\n return detectNetworkFsName(typeMagic);\n}\n\n/**\n * T5.8 — Test seam: clear the per-directory warn-once registry between\n * tests so warning-emission tests stay deterministic.\n *\n * @internal\n */\nexport function __TESTING__resetNfsWarnings(): void {\n warnedNfsDirs.clear();\n}\n\n/**\n * M107 — controle de criação do temporário, comum a `replaceFileAtomic`,\n * `atomicWriteJson` e `atomicWriteText`.\n *\n * Ambos os campos são OPCIONAIS e o default é byte-idêntico ao comportamento\n * anterior a M107. Ver `replaceFileAtomic` para por que a reafirmação de modo\n * é condicional.\n *\n * @internal\n */\nexport interface AtomicWriteFileOptions {\n /**\n * Bits de permissão do arquivo criado. Default: `0o600`, o literal fixo de\n * antes — filtrado pelo `umask`, como sempre foi. Quando informado, o modo é\n * reafirmado no descritor, de modo que o `umask` não pode silenciosamente\n * limpar bits que o chamador pediu.\n */\n mode?: number;\n /**\n * Criar o temporário com `wx` (criação exclusiva) em vez de `w`. Default:\n * `false` — a flag de antes. Com `true`, um temporário pré-existente vira\n * `EEXIST` em vez de ser truncado.\n */\n exclusive?: boolean;\n}\n\n/**\n * Atomic file replacement: write content to a per-call unique tmp path,\n * fsync, then rename over the target. Crash mid-write leaves either the old\n * file intact or the new file complete — never a half-written file.\n *\n * The tmp suffix is `<pid>.<rand>.tmp` so parallel processes (and concurrent\n * burst writes within one process) never collide on the same tmp path — a\n * race that would manifest as `ENOENT` on `rename` after the rival process\n * already moved its tmp into place.\n *\n * Mirrors peer-project's `replaceFileAtomic` from\n * `referencia/peer-project/packages/memory-host-sdk/src/host/fs-utils.ts` with\n * the multi-writer robustness fix.\n *\n * ## M107 — `options` é opcional, e o default é byte-idêntico\n *\n * O terceiro parâmetro é aditivo: todo chamador anterior continua compilando e\n * escrevendo exatamente o mesmo byte, com o mesmo modo, no mesmo caminho.\n *\n * A reafirmação de modo (`handle.chmod`) é **condicional a `mode !== undefined`**,\n * e isso não é cosmético. O argumento de modo do `open` é filtrado pelo `umask`,\n * que só LIMPA bits — medido nesta base de código antes da mudança:\n *\n * ```\n * umask 0o002 -> 0o600 umask 0o022 -> 0o600 umask 0o200 -> 0o400\n * ```\n *\n * Um `chmod` incondicional levaria o terceiro caso de `0o400` para `0o600` — uma\n * mudança de disco para todo chamador que não pediu nada, incluindo consumidores\n * externos. Quando o chamador PEDE um modo, porém, deixar o `umask` decidir em\n * silêncio é o defeito que este parâmetro existe para fechar; daí a reafirmação.\n *\n * Ela vai no DESCRITOR, antes do `rename`, nunca depois: dar `chmod` no arquivo\n * final deixaria uma janela em que ele está com o modo do `umask` — o anti-padrão\n * de `opencode/packages/core/src/fs-util.ts:110-114`. A forma escolhida (modo como\n * argumento do `open`) é a de `codex-rs/network-proxy/src/certs.rs:687,783-791`.\n *\n * @internal\n */\nexport async function replaceFileAtomic(\n filePath: string,\n content: string,\n options?: AtomicWriteFileOptions,\n): Promise<void> {\n // T5.8 — warn once per parent directory if it lives on a network /\n // FUSE filesystem where `rename()` atomicity is best-effort. The\n // write proceeds unchanged; the warning is purely informational so\n // operators can spot the case in stderr / log aggregators.\n await warnOnNetworkFsOnce(dirname(filePath), \"atomic-write\");\n // T5.7 — crypto-random tmp suffix (CSPRNG, 64 bits of entropy)\n // replaces the predictable `Math.random().toString(36)` source. An\n // attacker observing the process can no longer predict the next\n // tmp path and pre-stage a hostile file to be renamed into place.\n const suffix = randomBytes(8).toString(\"hex\");\n const tmp = `${filePath}.${process.pid}.${suffix}.tmp`;\n // T5.7 — mode 0o600 on the tmp file (owner read+write only). The\n // tmp file holds the FULL in-flight content (credential snapshots,\n // OAuth tokens) before the rename. World-readable default would\n // expose secrets during the ms-window between open and rename\n // (TOCTOU). On modern Linux the post-rename target inherits the\n // tmp's permission bits, so the final file is also 0o600.\n const handle = await open(tmp, options?.exclusive === true ? \"wx\" : \"w\", options?.mode ?? 0o600);\n try {\n await handle.writeFile(content, \"utf8\");\n await handle.sync();\n // Condicional por medição, não por gosto — ver o docblock desta função.\n if (options?.mode !== undefined) await handle.chmod(options.mode);\n } finally {\n await handle.close();\n }\n try {\n await rename(tmp, filePath);\n } catch (cause) {\n // Cleanup tmp on rename failure so we don't leak stale .tmp files.\n await unlink(tmp).catch(() => undefined);\n throw cause;\n }\n}\n\n/**\n * Options for `atomicWriteJson`.\n *\n * @internal\n */\nexport interface AtomicWriteJsonOptions extends AtomicWriteFileOptions {\n /** Indent passed to `JSON.stringify`. Default: 2. */\n indent?: number;\n /** Whether to append a trailing newline (POSIX convention). Default: true. */\n trailingNewline?: boolean;\n}\n\n/**\n * Typed JSON atomic write helper.\n *\n * Serializes `data` to JSON, then delegates to `replaceFileAtomic`. The\n * parent directory is auto-created (recursive `mkdir`) to make this helper\n * safe for callers who haven't ensured the directory exists (EC-4 in the\n * persistence-state-hardening plan).\n *\n * Throws `TypeError` on circular refs or `undefined` data (propagates from\n * `JSON.stringify`).\n *\n * @internal\n */\nexport async function atomicWriteJson<T>(\n filePath: string,\n data: T,\n options?: AtomicWriteJsonOptions,\n): Promise<void> {\n const indent = options?.indent ?? 2;\n const trailingNewline = options?.trailingNewline ?? true;\n const json = JSON.stringify(data, null, indent);\n if (json === undefined) {\n throw new TypeError(\"atomicWriteJson: cannot serialize undefined\");\n }\n const content = trailingNewline ? `${json}\\n` : json;\n await mkdir(dirname(filePath), { recursive: true });\n await replaceFileAtomic(filePath, content, options);\n}\n\n/**\n * Atomic text write. Same crash-safety guarantees as `replaceFileAtomic` +\n * auto-mkdir of the parent directory. Used by `theokit-migrate-config`\n * (T4.1, EC-2 MUST FIX) so a crash mid-migration leaves previous MD files\n * intact rather than corrupting them.\n *\n * @internal\n */\nexport async function atomicWriteText(filePath: string, content: string): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true });\n await replaceFileAtomic(filePath, content);\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/session/session-cache.ts"],"names":[],"mappings":";;;AAgBO,IAAM,QAAA,uBAAe,GAAA;AACrB,IAAM,YAAA,uBAAmB,GAAA;AAGzB,SAAS,aAAA,CAAc,KAAa,OAAA,EAAyB;AAClE,EAAA,OAAO,CAAA,EAAG,GAAG,CAAA,EAAA,EAAK,OAAO,CAAA,CAAA;AAC3B;AAEO,SAAS,sBAAA,CAAuB,KAAa,OAAA,EAAuB;AACzE,EAAA,QAAA,CAAS,OAAO,OAAO,CAAA;AACvB,EAAA,YAAA,CAAa,MAAA,CAAO,aAAA,CAAc,GAAA,EAAK,OAAO,CAAC,CAAA;AACjD","file":"chunk-IT3PWBW4.cjs","sourcesContent":["import type { SessionMessage } from \"./session-types.js\";\n\n/**\n * O cache de sessão em memória — num módulo FOLHA, de propósito.\n *\n * O estado e o invalidador viviam em `agent-session.ts`, e `compact-session.ts` importava\n * `invalidateSessionCache` de lá — enquanto `agent-session.ts` importava `autoCompactIfNeeded` de\n * volta. O ciclo estava quebrado em runtime (a volta é `await import()` dinâmico), mas o detector\n * conta a aresta dinâmica, e contar import dinâmico como ciclo torna o gate impossível de satisfazer\n * sem abandonar a técnica canônica de quebrar ciclos.\n *\n * O dono natural deste estado nunca foi `agent-session.ts`: são dois mapas de processo que ambos os\n * módulos consultam. Extraí-los para uma folha remove o ciclo em QUALQUER detector, sem política de\n * ferramenta e sem mudar uma linha de comportamento — as mesmas instâncias de `Map`/`Set` continuam\n * sendo as únicas do processo, porque módulo ES é singleton.\n */\nexport const sessions = new Map<string, SessionMessage[]>();\nexport const hydratedKeys = new Set<string>();\n\n/** The per-(cwd, agentId) transcript key for cache/hydration bookkeeping. */\nexport function transcriptKey(cwd: string, agentId: string): string {\n return `${cwd}::${agentId}`;\n}\n\nexport function invalidateSessionCache(cwd: string, agentId: string): void {\n sessions.delete(agentId);\n hydratedKeys.delete(transcriptKey(cwd, agentId));\n}\n"]}
|
package/dist/chunk-ITLS5LRF.cjs
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chunkP34DHHYQ_cjs = require('./chunk-P34DHHYQ.cjs');
|
|
4
|
-
var chunkTQI3KRXZ_cjs = require('./chunk-TQI3KRXZ.cjs');
|
|
5
|
-
var chunkIEUDKUPM_cjs = require('./chunk-IEUDKUPM.cjs');
|
|
6
|
-
var promises = require('fs/promises');
|
|
7
|
-
var path = require('path');
|
|
8
|
-
|
|
9
|
-
var compartilhados = /* @__PURE__ */ new Map();
|
|
10
|
-
async function adquirirCompartilhado(path) {
|
|
11
|
-
const existente = compartilhados.get(path);
|
|
12
|
-
if (existente !== void 0) {
|
|
13
|
-
existente.refs++;
|
|
14
|
-
return criarProxy(path);
|
|
15
|
-
}
|
|
16
|
-
const lease = await chunkP34DHHYQ_cjs.acquireSessionWriter(path);
|
|
17
|
-
compartilhados.set(path, { lease, refs: 1 });
|
|
18
|
-
return criarProxy(path);
|
|
19
|
-
}
|
|
20
|
-
function criarProxy(path) {
|
|
21
|
-
let solto = false;
|
|
22
|
-
return {
|
|
23
|
-
sessionPath: path,
|
|
24
|
-
release: async () => {
|
|
25
|
-
if (solto) return;
|
|
26
|
-
solto = true;
|
|
27
|
-
const entrada = compartilhados.get(path);
|
|
28
|
-
if (entrada === void 0) return;
|
|
29
|
-
entrada.refs--;
|
|
30
|
-
if (entrada.refs > 0) return;
|
|
31
|
-
compartilhados.delete(path);
|
|
32
|
-
await entrada.lease.release();
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
var FsSessionStore = class {
|
|
37
|
-
#baseDir;
|
|
38
|
-
#cwd;
|
|
39
|
-
/** Um lease por `agentId` — um store serve mais de uma sessão ao longo da vida do processo. */
|
|
40
|
-
#leases = /* @__PURE__ */ new Map();
|
|
41
|
-
constructor(options) {
|
|
42
|
-
this.#baseDir = options.baseDir;
|
|
43
|
-
this.#cwd = options.cwd;
|
|
44
|
-
}
|
|
45
|
-
async readRecords(agentId) {
|
|
46
|
-
return chunkIEUDKUPM_cjs.readTranscript(chunkIEUDKUPM_cjs.transcriptPath(this.#baseDir, this.#cwd, agentId));
|
|
47
|
-
}
|
|
48
|
-
async appendRecords(agentId, records) {
|
|
49
|
-
if (records.length === 0) return;
|
|
50
|
-
const path$1 = chunkIEUDKUPM_cjs.transcriptPath(this.#baseDir, this.#cwd, agentId);
|
|
51
|
-
await promises.mkdir(path.dirname(path$1), { recursive: true });
|
|
52
|
-
await chunkTQI3KRXZ_cjs.withFileLock(path$1, async () => {
|
|
53
|
-
for (const record of records) chunkTQI3KRXZ_cjs.appendJsonl(path$1, record);
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Toma o lease de escritor da sessão. Lança `SessionBusyError` quando outro processo a detém.
|
|
58
|
-
*
|
|
59
|
-
* **Explícito, e NÃO no `appendRecords`** — a revisão adversarial do M95 mediu por que isso
|
|
60
|
-
* importa: o contrato de `SessionStore` diz que "an `appendRecords` rejection is logged to
|
|
61
|
-
* stderr, NOT thrown to the caller (best-effort write)". Adquirir ali fazia o `SessionBusyError`
|
|
62
|
-
* ser **engolido**, e o resultado era pior que o problema original: em vez de dois escritores
|
|
63
|
-
* intercalarem linhas, o perdedor **perdia o turno em silêncio** — nada em disco, um aviso em
|
|
64
|
-
* stderr invisível sob a TUI, e o chamador sem como reagir.
|
|
65
|
-
*
|
|
66
|
-
* No init o erro chega a quem pode agir: o `exec` forka para um id novo, que é o que a própria
|
|
67
|
-
* mensagem do erro prescreve. É a diferença entre falhar onde dá para decidir e falhar onde só
|
|
68
|
-
* dá para perder.
|
|
69
|
-
*/
|
|
70
|
-
async acquire(agentId) {
|
|
71
|
-
if (this.#leases.has(agentId)) return;
|
|
72
|
-
const path$1 = chunkIEUDKUPM_cjs.transcriptPath(this.#baseDir, this.#cwd, agentId);
|
|
73
|
-
await promises.mkdir(path.dirname(path$1), { recursive: true });
|
|
74
|
-
this.#leases.set(agentId, await adquirirCompartilhado(path$1));
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Solta o lease de UM agente.
|
|
78
|
-
*
|
|
79
|
-
* Existe porque `dispose()` solta **todos** os leases do store, e um store injetado pelo
|
|
80
|
-
* consumidor pode servir vários agentes: um init que falha para o agente B não pode liberar o
|
|
81
|
-
* lease do agente A, que segue vivo e escrevendo.
|
|
82
|
-
*/
|
|
83
|
-
async release(agentId) {
|
|
84
|
-
const lease = this.#leases.get(agentId);
|
|
85
|
-
if (lease === void 0) return;
|
|
86
|
-
this.#leases.delete(agentId);
|
|
87
|
-
await lease.release();
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Solta todo lease que este store detém.
|
|
91
|
-
*
|
|
92
|
-
* Sem isto o `.writer.lock` sobrevive ao processo e a próxima abertura teria de esperar a janela
|
|
93
|
-
* de heartbeat — recuperável, mas 30 s de espera para um encerramento LIMPO seria um defeito
|
|
94
|
-
* evitável. Idempotente: chamar duas vezes não é erro.
|
|
95
|
-
*/
|
|
96
|
-
async dispose() {
|
|
97
|
-
const leases = [...this.#leases.values()];
|
|
98
|
-
this.#leases.clear();
|
|
99
|
-
for (const lease of leases) await lease.release();
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
exports.FsSessionStore = FsSessionStore;
|
|
104
|
-
//# sourceMappingURL=chunk-ITLS5LRF.cjs.map
|
|
105
|
-
//# sourceMappingURL=chunk-ITLS5LRF.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/persistence/fs-session-store.ts"],"names":["acquireSessionWriter","readTranscript","transcriptPath","path","mkdir","dirname","withFileLock","appendJsonl"],"mappings":"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","file":"chunk-ITLS5LRF.cjs","sourcesContent":["/**\n * SE41 — `FsSessionStore`, the DEFAULT reference implementation of the pluggable\n * {@link SessionStore} seam. It reads and append-writes the native Claude-shaped\n * `.jsonl` transcript at `<baseDir>/projects/<encoded-cwd>/<agentId>.jsonl` — the\n * exact on-disk format SE40 introduced (the file the Claude Code CLI can\n * `--continue`). Omitting `local.sessionStore` resolves to this store, so the\n * default persistence path is byte-identical to SE40 behavior.\n *\n * `readRecords` is `readTranscript(transcriptPath(...))` (a missing session →\n * `[]`, not an error — a fresh agent has no history). `appendRecords` is a TRUE\n * append: it reads the prior records, concatenates the new-turn delta, and\n * rewrites the whole line set atomically under the SE40 cross-process file lock\n * (`writeTranscript` never shrinks — the native format is an append-only\n * `parentUuid` DAG). The parent dir is created BEFORE acquiring the lock because\n * the lock's companion `<path>.lock` file needs an existing parent dir (the SE40\n * `mkdir(dirname)`-before-lock fix).\n *\n * @internal\n */\n\nimport { mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { SessionStore } from \"../../types/session-store.js\";\nimport { withFileLock } from \"./file-lock.js\";\nimport { appendJsonl } from \"./jsonl.js\";\nimport { readTranscript, type SessionRecord, transcriptPath } from \"./session-transcript.js\";\nimport { acquireSessionWriter, type SessionWriterLease } from \"./session-writer.js\";\n\n/** Options identifying the on-disk transcript location for the FS default store. */\nexport interface FsSessionStoreOptions {\n /** Transcript base dir (`~/.theokit` default, `~/.claude` for CLI interop) — already `~`-expanded. */\n baseDir: string;\n /** The workspace cwd whose encoded form is the transcript project dir. */\n cwd: string;\n}\n\n/**\n * The default `SessionStore` — reads/append-writes the native `.jsonl` transcript.\n *\n * @internal\n */\n/**\n * Leases compartilhados por caminho, com contagem de referências.\n *\n * `acquireSessionWriter` é ESTRITO de propósito: duas aquisições concorrentes do mesmo caminho, e\n * exatamente uma vence — \"um lease que deixasse as duas passarem seria decorativo\" (o teste do M81\n * diz isso com todas as letras, e está certo). Esse é o contrato do primitivo, e ele não muda.\n *\n * Mas dentro de UM processo é normal existir mais de um store sobre a mesma sessão: os testes\n * golden de compactação e de sends concorrentes fazem exatamente isso, e são registro de\n * comportamento real do runtime. Aplicar o primitivo cru ali transformaria um padrão legítimo em\n * `SessionBusyError`.\n *\n * A conciliação é aqui, no consumidor do primitivo: o processo tira UM lease por caminho e conta\n * quantos stores o usam. O último a soltar libera de fato. Cross-process continua estrito — que é o\n * problema que o M81 enuncia (\"`exec resume --last` pode escrever na sessão viva da TUI\").\n */\nconst compartilhados = new Map<string, { lease: SessionWriterLease; refs: number }>();\n\nasync function adquirirCompartilhado(path: string): Promise<SessionWriterLease> {\n const existente = compartilhados.get(path);\n if (existente !== undefined) {\n existente.refs++;\n return criarProxy(path);\n }\n const lease = await acquireSessionWriter(path);\n compartilhados.set(path, { lease, refs: 1 });\n return criarProxy(path);\n}\n\n/** Um handle que decrementa a contagem; o último a soltar libera o lease de verdade. */\nfunction criarProxy(path: string): SessionWriterLease {\n let solto = false;\n return {\n sessionPath: path,\n release: async (): Promise<void> => {\n if (solto) return;\n solto = true;\n const entrada = compartilhados.get(path);\n if (entrada === undefined) return;\n entrada.refs--;\n if (entrada.refs > 0) return;\n compartilhados.delete(path);\n await entrada.lease.release();\n },\n };\n}\n\nexport class FsSessionStore implements SessionStore {\n readonly #baseDir: string;\n readonly #cwd: string;\n /** Um lease por `agentId` — um store serve mais de uma sessão ao longo da vida do processo. */\n readonly #leases = new Map<string, SessionWriterLease>();\n\n constructor(options: FsSessionStoreOptions) {\n this.#baseDir = options.baseDir;\n this.#cwd = options.cwd;\n }\n\n async readRecords(agentId: string): Promise<SessionRecord[]> {\n return readTranscript(transcriptPath(this.#baseDir, this.#cwd, agentId));\n }\n\n async appendRecords(agentId: string, records: readonly SessionRecord[]): Promise<void> {\n // Empty delta → nothing to persist (avoids an unnecessary lock + rewrite).\n if (records.length === 0) return;\n const path = transcriptPath(this.#baseDir, this.#cwd, agentId);\n // mkdir BEFORE the lock: withFileLock's companion `<path>.lock` needs the parent dir.\n await mkdir(dirname(path), { recursive: true });\n\n await withFileLock(path, async () => {\n // M93 — acrescenta o DELTA em vez de reescrever o arquivo inteiro.\n //\n // Antes: `readTranscript` + `writeTranscript` de tudo, por turno. O(n) de I/O **e** de parse a\n // cada turno, O(n²) por sessão — a nota do consumidor em `agents/lib/session/backtrack.ts`\n // registra 1,4 MB / 3000 linhas em 200 turnos.\n //\n // Correto porque o formato **já é append-only**: o DAG de `parentUuid` não depende da ordem de\n // linha, e cada registro carrega o próprio pai. `appendJsonl` **já existia no pacote** e tinha um\n // único chamador (`eval/runner.ts`) — a primitiva estava lá, o store é que a ignorava (rung 4).\n //\n // O `withFileLock` permanece — mas a afirmação anterior de que \"ele é o que serializa dois\n // `appendRecords` concorrentes\" era forte demais, e a revisão adversarial do M93 mediu isso:\n // removê-lo não reprova nenhum teste. A razão é o próprio parágrafo acima — o DAG de\n // `parentUuid` não depende da ordem de linha, então dois lotes intercalados reconstroem igual.\n // O trabalho que o lock fazia (proteger um read-modify-write) sumiu junto com o rewrite.\n //\n // O que ele ainda cobre é a janela TOCTOU de `precisaDeQuebraAntes` (ler o último byte, depois\n // escrever): sem ele, dois processos podem ambos concluir \"falta \\n\" e produzir uma linha em\n // branco — que o leitor descarta, isto é, benigno. Fica como **defesa declarada, não\n // mecanizada** (a disciplina de `error-handling.md § 4`: enumerar o resíduo em vez de deixar a\n // ausência de teste passar por cobertura).\n //\n // `writeTranscript` continua existindo para **compactação**, a única operação que legitimamente\n // reescreve o arquivo.\n for (const record of records) appendJsonl(path, record);\n });\n }\n\n /**\n * Toma o lease de escritor da sessão. Lança `SessionBusyError` quando outro processo a detém.\n *\n * **Explícito, e NÃO no `appendRecords`** — a revisão adversarial do M95 mediu por que isso\n * importa: o contrato de `SessionStore` diz que \"an `appendRecords` rejection is logged to\n * stderr, NOT thrown to the caller (best-effort write)\". Adquirir ali fazia o `SessionBusyError`\n * ser **engolido**, e o resultado era pior que o problema original: em vez de dois escritores\n * intercalarem linhas, o perdedor **perdia o turno em silêncio** — nada em disco, um aviso em\n * stderr invisível sob a TUI, e o chamador sem como reagir.\n *\n * No init o erro chega a quem pode agir: o `exec` forka para um id novo, que é o que a própria\n * mensagem do erro prescreve. É a diferença entre falhar onde dá para decidir e falhar onde só\n * dá para perder.\n */\n async acquire(agentId: string): Promise<void> {\n if (this.#leases.has(agentId)) return;\n const path = transcriptPath(this.#baseDir, this.#cwd, agentId);\n await mkdir(dirname(path), { recursive: true });\n this.#leases.set(agentId, await adquirirCompartilhado(path));\n }\n\n /**\n * Solta o lease de UM agente.\n *\n * Existe porque `dispose()` solta **todos** os leases do store, e um store injetado pelo\n * consumidor pode servir vários agentes: um init que falha para o agente B não pode liberar o\n * lease do agente A, que segue vivo e escrevendo.\n */\n async release(agentId: string): Promise<void> {\n const lease = this.#leases.get(agentId);\n if (lease === undefined) return;\n this.#leases.delete(agentId);\n await lease.release();\n }\n\n /**\n * Solta todo lease que este store detém.\n *\n * Sem isto o `.writer.lock` sobrevive ao processo e a próxima abertura teria de esperar a janela\n * de heartbeat — recuperável, mas 30 s de espera para um encerramento LIMPO seria um defeito\n * evitável. Idempotente: chamar duas vezes não é erro.\n */\n async dispose(): Promise<void> {\n const leases = [...this.#leases.values()];\n this.#leases.clear();\n for (const lease of leases) await lease.release();\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/diagnostics.ts","../src/internal/security/redact.ts"],"names":[],"mappings":"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","file":"chunk-LTCOPATU.cjs","sourcesContent":["/**\n * Canal único de diagnóstico da biblioteca — silencioso por padrão (#147).\n *\n * ## O problema\n *\n * O SDK escrevia diagnósticos direto em `process.stderr` a partir de caminhos quentes —\n * 92 sítios em 51 arquivos sob `internal/`. Numa host de TUI (Ink, alternate screen), essas\n * escritas se intercalam com o render e **corrompem o frame**. E o host não tinha como\n * interceptá-las: não havia logger injetável. Um consumidor chegou a instalar\n * `proper-lockfile` só para calar UMA delas.\n *\n * Uma biblioteca não pode assumir que `stdout`/`stderr` são sinks livres. Quem é dono do\n * terminal é a aplicação, não a dependência.\n *\n * ## O contrato\n *\n * - **`setDiagnosticsSink(fn)`** entrega as mensagens à aplicação, que decide onde colocá-las\n * (uma linha de status, um arquivo, um painel). É o que faltava para uma TUI conviver com o SDK.\n * - **Sem sink, vai para o `stderr`** — exatamente como antes desta mudança.\n *\n * ## Por que o padrão NÃO mudou (ainda), e isso é deliberado\n *\n * A #147 pede silêncio por padrão, e é o alvo certo. Mas **58 arquivos de teste** asseram hoje\n * que estes diagnósticos chegam ao `stderr` — eles codificam o contrato \"o aviso É emitido\", que\n * continua valendo e não deve ser perdido. Virar o padrão sem migrá-los reprovaria ~53 testes, e\n * migrá-los às pressas, em quatro formas diferentes de espionar o `stderr`, é a receita para\n * enfraquecer 58 suítes de uma vez.\n *\n * Então esta mudança entrega a metade que DESTRAVA o consumidor — o host de TUI agora consegue\n * interceptar, que era o bloqueio relatado (\"no way to intercept these; no injectable logger\") — e\n * deixa a virada do padrão como migração própria, com custo medido. Um host que quer silêncio\n * hoje instala `setDiagnosticsSink(() => {})`.\n *\n * ## O que isto NÃO é\n *\n * Não é um logger com níveis, formatação ou destinos múltiplos. É o mínimo que resolve o\n * bloqueio relatado; um logger completo aqui seria inventar requisito que ninguém pediu.\n */\n\n/** Recebe cada mensagem de diagnóstico já formatada, com o `\\n` final. */\nexport type DiagnosticsSink = (message: string) => void;\n\nlet sink: DiagnosticsSink | undefined;\n\n/**\n * Instala (ou remove, passando `undefined`) o destino dos diagnósticos.\n *\n * Quando há sink, ele é o ÚNICO destino — o `stderr` não recebe cópia. Duplicar destinos\n * devolveria o problema à TUI que instalou o sink justamente para tirar as mensagens do terminal.\n */\nexport function setDiagnosticsSink(next: DiagnosticsSink | undefined): void {\n sink = next;\n}\n\n/**\n * Emite uma mensagem de diagnóstico da biblioteca.\n *\n * Substitui `process.stderr.write` nos caminhos internos. Nunca lança: um sink defeituoso não\n * pode derrubar o run que ele apenas observa.\n */\nexport function diag(message: string): void {\n if (sink !== undefined) {\n try {\n sink(message);\n } catch {\n // Observabilidade nunca quebra o run — mesmo princípio de `emitRunEvent`.\n }\n return;\n }\n process.stderr.write(message);\n}\n","import { diag } from \"../diagnostics.js\";\n\n/**\n * Canonical secret redaction module (ADRs D68-D73).\n *\n * Single source of truth for credential pattern masking across the SDK.\n * Wired at output boundaries: `ErrorMetadata.raw` (mappers/shared.ts),\n * telemetry span attributes (telemetry/tracer.ts), transcript JSONL\n * appends (agent-session-store.ts), migration logger output\n * (memory/migrate-sqlite-to-lance.ts).\n *\n * - D68: central module, single source of truth (replaces 2 duplicates)\n * - D69: env snapshot at module init (prompt-injection defense)\n * - D70: ON by default, warn on opt-out\n * - D71: two-bucket masking — short fully masked, long preserves prefix+suffix\n * - D72: `codeFile` opt-out for legitimate prefix-shaped content\n * - D73: redact at OUTPUT boundaries, not at storage\n *\n * @internal\n */\n\n// D69: env snapshot captured at module load. Subsequent mutations of\n// process.env.THEOKIT_REDACT_SECRETS are ignored — defends against\n// prompt injection that tries to disable redaction mid-run.\nlet REDACT_ENABLED: boolean = readEnvOnce();\n\nfunction readEnvOnce(): boolean {\n const raw = process.env.THEOKIT_REDACT_SECRETS;\n if (raw === undefined) return true; // D70: default ON\n return [\"1\", \"true\", \"yes\", \"on\"].includes(raw.toLowerCase());\n}\n\n// D70: warn once on opt-out so the user knows they're vulnerable.\nlet warnedOptOut = false;\nif (!REDACT_ENABLED && !warnedOptOut) {\n diag(\n \"[theokit-sdk] Secret redaction is DISABLED via THEOKIT_REDACT_SECRETS. \" +\n \"Credentials may leak into errors, telemetry, logs, transcripts.\\n\",\n );\n warnedOptOut = true;\n}\n\n/**\n * Built-in credential patterns. Order matters — more specific prefixes\n * must come before generic ones (e.g., `sk-ant-` before `sk-`). Quantifiers\n * are all bounded `{n,m}` or applied to char classes — linear time, no ReDoS.\n *\n * @internal\n */\nconst BUILTIN_PATTERNS: readonly RegExp[] = [\n // T5.4: 30+ vendor prefixes (was 12 pre-T5.4). Order matters — more\n // specific prefixes precede generic ones (e.g., sk-ant-admin01 before\n // sk-ant-, sk-proj- before sk-). PEM block deliberately first so its\n // multi-line span runs before any per-line patterns can fire.\n /-----BEGIN[ ]+(?:RSA |EC |DSA |OPENSSH |ENCRYPTED |)PRIVATE KEY-----[\\s\\S]+?-----END[ ]+(?:RSA |EC |DSA |OPENSSH |ENCRYPTED |)PRIVATE KEY-----/g,\n // JWT — exact 3-segment base64url. Dotted; the body floor of 4 chars per\n // segment matches the minimum legal payload while skipping `a.b.c` noise.\n /eyJ[A-Za-z0-9_-]{4,}\\.eyJ[A-Za-z0-9_-]{4,}\\.[A-Za-z0-9_-]{4,}/g,\n // Azure Storage SAS — match the sig= component (URL-encoded base64).\n /(?<=[?&]sig=)[A-Za-z0-9%+/]{20,}/g,\n // Anthropic\n /sk-ant-admin01-[A-Za-z0-9_-]{10,}/g, // Anthropic admin keys (must precede sk-ant-)\n /sk-ant-[A-Za-z0-9_-]{10,}/g, // Anthropic regular\n // OpenAI family + clones (sk- generic must come AFTER all sk-foo- variants)\n /sk-proj-[A-Za-z0-9_-]{10,}/g, // OpenAI project key (must precede sk- generic)\n /sk-[A-Za-z0-9_-]{10,}/g, // OpenAI / OpenRouter / DeepInfra / Together / DeepSeek\n // Provider prefixes (alphabetized for maintainability)\n /AIza[A-Za-z0-9_-]{35}/g, // Google API key\n /AKIA[A-Z0-9]{16}/g, // AWS access key\n /fw_[A-Za-z0-9]{20,}/g, // Fireworks\n /glpat-[A-Za-z0-9_-]{20}/g, // GitLab PAT\n /ghp_[A-Za-z0-9]{36}/g, // GitHub PAT classic\n /github_pat_[A-Za-z0-9_]{82}/g, // GitHub PAT fine-grained\n /gsk_[A-Za-z0-9]{20,}/g, // Groq\n /hf_[A-Za-z0-9]{20,}/g, // HuggingFace\n /\\bpa-[A-Za-z0-9_-]{20,}/g, // Voyage AI (word-boundary to skip CSS / kebab IDs)\n /pcsk_[A-Za-z0-9_-]{20,}/g, // Pinecone\n /pplx-[A-Za-z0-9_-]{20,}/g, // Perplexity\n /r8_[A-Za-z0-9_-]{20,}/g, // Replicate\n /rk_live_[A-Za-z0-9]{20,}/g, // Stripe restricted\n /sk_live_[A-Za-z0-9]{20,}/g, // Stripe secret\n /sntrys_[A-Za-z0-9]{40,}/g, // Sentry user auth\n /xai-[A-Za-z0-9_-]{20,}/g, // xAI (Grok)\n /xox[bpasr]-[A-Za-z0-9-]{10,}/g, //Slack tokens\n // Additional unique-prefix tokens with low false-positive risk\n /npm_[A-Za-z0-9]{36}/g, // npm access token\n /SG\\.[A-Za-z0-9_-]{22}\\.[A-Za-z0-9_-]{43}/g, // SendGrid\n /\\bSK[A-Za-z0-9]{32}\\b/g, // Twilio API SID (word-boundary to skip CSS class noise)\n /\\bkey-[a-f0-9]{32}\\b/g, // Mailgun (hex-only narrows false positives)\n /MT[A-Za-z0-9_-]{23}\\.[A-Za-z0-9_-]{6}\\.[A-Za-z0-9_-]{27}/g, // Discord bot\n /\\b(?:sdk|mob)-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\\b/g, // LaunchDarkly\n];\n\n// `Bearer <token>` matched as its own first-class pattern so PARAM_PATTERN\n// doesn't have to handle the unusual `Authorization: Bearer xxx` shape\n// (no `:` or `=` between \"Bearer\" and the value — bare whitespace).\nconst BEARER_PATTERN = /\\b(Bearer\\s+)([A-Za-z0-9_\\-.+/=]{8,})/g;\n\n// Parametric: matches `key=value` and `key: value` (with optional quote\n// between the key and the separator, to handle JSON: `\"api_key\": \"...\"`)\n// in URLs, query strings, JSON-like bodies, HTTP headers. Captures the\n// prefix so we keep it visible while masking the value.\n//\n// `authorization` deliberately excluded — BEARER_PATTERN handles the\n// common `Authorization: Bearer xxx` shape. Including it here causes\n// double-masking (\"Authorization: *** ***\") after Bearer fires.\n// T5.4: keyword set expanded from 6 → 16 to cover the OAuth / JWT / generic\n// credential vocabulary surfaced by DR6 finding #4. `authorization`,\n// `auth`, `bearer` stay excluded — BEARER_PATTERN handles the\n// `Authorization: Bearer xxx` shape and including these here would\n// re-catch the post-BUILTIN-masked form (D71 prefix-preservation\n// contract) and double-mask to `***`.\n//\n// Value class includes `.` so JWT / `.env` / dotted base64url values\n// match; the callback skips already-masked values (containing the\n// `...` D71 separator) to preserve the BUILTIN prefix-mask result.\nconst PARAM_PATTERN =\n /(\\b(?:access_token|api_key|api-key|client_secret|credential|credentials|id_token|jwt|password|private_key|refresh_token|secret|service_account|session_token|token|x-api-key)\\b[\"']?\\s*[:=]\\s*[\"']?)([A-Za-z0-9_\\-.+/]+)/gi;\n\nconst _extraPatterns: RegExp[] = [];\n\n/**\n * Add a user-defined redaction pattern. Additive — never removes builtins.\n * Throws if the regex lacks the `/g` flag (without `/g`, `.replace` only\n * substitutes the first match and the rest leaks).\n *\n * @internal — exposed publicly via `Security.addPattern` in `src/security.ts`.\n */\nexport function addPattern(re: RegExp): void {\n if (!re.global) {\n throw new Error(\"Security.addPattern: regex must have /g flag for replace-all semantics\");\n }\n _extraPatterns.push(re);\n}\n\n/**\n * Two-bucket masking (D71):\n * - tokens shorter than 18 chars → fully masked as `***`\n * - tokens >= 18 chars → keep first 6 + `...` + last 4\n *\n * Rationale: long tokens are unique per-account; prefix+suffix preserves\n * debuggability without revealing the secret middle.\n *\n * @internal\n */\nexport function maskToken(token: string): string {\n if (token.length < 18) return \"***\";\n return `${token.slice(0, 6)}...${token.slice(-4)}`;\n}\n\n// issue #117 — the EXACT shape of a `maskToken` output (`slice(0,6)+\"...\"+slice(-4)`\n// = 6 chars, literal `...`, 4 chars). Used to detect a value PARAM_PATTERN would\n// otherwise re-mask, WITHOUT skipping a raw secret that merely contains `...`.\nconst MASK_SHAPE = /^.{6}\\.\\.\\..{4}$/s;\n\n/**\n * Redact known credential patterns from `text`. Default behavior masks\n * builtins + extras + parametric `key=value` sinks.\n *\n * With `{ codeFile: true }` (D72), skips PARAM_PATTERN to avoid mangling\n * `.env.example`, schema JSON, or test fixtures that legitimately contain\n * prefix-like strings.\n *\n * Returns the redacted string. Coerces non-strings via JSON.stringify;\n * EC-7 fix (edge-case review): wraps in try/catch so circular references\n * never propagate — returns sentinel `\"[unredactable: circular]\"`.\n *\n * @internal\n */\n// Coerce arbitrary input to a string for redaction. Returns `null`\n// sentinel when the value is null/undefined/non-stringifiable, so the\n// caller can short-circuit with `\"\"`. EC-7 fix: circular refs go through\n// the try/catch and produce the sentinel marker, never throwing.\nfunction coerceToString(value: unknown): string | null {\n if (typeof value === \"string\") return value;\n if (value === null || value === undefined) return null;\n if (typeof value === \"object\") {\n try {\n const s = JSON.stringify(value);\n return s === undefined ? null : s;\n } catch {\n return \"[unredactable: circular]\";\n }\n }\n return String(value);\n}\n\nexport function redactSecrets(text: unknown, opts?: { codeFile?: boolean }): string {\n const coerced = coerceToString(text);\n if (coerced === null) return \"\";\n if (!REDACT_ENABLED) return coerced;\n\n let s = coerced;\n for (const re of BUILTIN_PATTERNS) {\n s = s.replace(re, (m) => maskToken(m));\n }\n for (const re of _extraPatterns) {\n s = s.replace(re, (m) => maskToken(m));\n }\n if (!opts?.codeFile) {\n // Bearer first (preserves \"Bearer \" prefix, masks the token after).\n // Must run before PARAM_PATTERN so the bare-whitespace shape doesn't\n // get mis-handled as a value.\n s = s.replace(BEARER_PATTERN, (_, prefix: string) => `${prefix}***`);\n // T5.4: skip if value already contains the D71 bucket-mask separator\n // (`...`) — BUILTIN ran first and produced a prefix-preserved mask;\n // re-masking would lose the prefix and degrade debuggability.\n s = s.replace(PARAM_PATTERN, (whole, prefix: string, value: string) => {\n // issue #117 — skip ONLY when the value is already a mask that a BUILTIN\n // pattern produced (maskToken's exact `6chars...4chars` shape), so we don't\n // re-mask and lose the prefix. The old `value.includes(\"...\")` was too broad:\n // a REAL secret that happens to contain `...` (e.g. `L_-cxw-.2UI_..._`) was\n // skipped and LEAKED. The mask shape is exact (maskToken: slice(0,6)+\"...\"+\n // slice(-4)), so a raw secret with `...` elsewhere is now masked.\n if (MASK_SHAPE.test(value)) return whole;\n return `${prefix}***`;\n });\n }\n return s;\n}\n\n/**\n * Test-only helper exported for `_test-reset.ts`. NOT included in the\n * `index.ts` barrel — vitest setup imports the dedicated module via\n * explicit path to discourage production callers.\n *\n * @internal\n */\nexport function _resetForTests(opts: { enabled?: boolean; clearExtras?: boolean }): void {\n if (opts.enabled !== undefined) REDACT_ENABLED = opts.enabled;\n if (opts.clearExtras === true) _extraPatterns.length = 0;\n}\n\n/**\n * T5.4 — Test-only count of BUILTIN_PATTERNS. Exposed so the count-floor\n * assertion can run without re-deriving the array shape in test land.\n * NOT included in the public barrel.\n *\n * @internal\n */\nexport function __TESTING__BUILTIN_PATTERN_COUNT(): number {\n return BUILTIN_PATTERNS.length;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/persistence/session-writer.ts"],"names":[],"mappings":"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","file":"chunk-MXSMAGDU.js","sourcesContent":["/**\n * M81 — single-writer lease for a session transcript.\n *\n * ## The problem\n *\n * Nothing stops two processes appending to the same JSONL transcript. The concrete case: `exec\n * resume --last` can write into the TUI's live session. Two interleaved appends to an append-only\n * file produce lines that are each individually valid and whose SEQUENCE is fiction — and nothing\n * reports it, because every line parses.\n *\n * ## Why an exclusive lockfile rather than `withFileLock`\n *\n * The plan's ADR D2 said to compose `withFileLock`, and that was the right instinct — do not build a\n * second lock mechanism. It turned out not to fit the SHAPE: `withFileLock(path, fn)` is\n * scope-based — it holds the lock for the duration of a callback. A session lease is **held across\n * turns**, for as long as the process owns the session, with an explicit `release()`. Wrapping the\n * whole session lifetime in a callback would invert control of the entire agent loop.\n *\n * So this uses the same underlying primitive `withFileLock` uses (an exclusive-create lockfile,\n * `wx`) with lease semantics on top. That keeps the mechanism single — the file-existence lock —\n * while giving it the lifetime the caller needs. The deviation from D2 is recorded here because the\n * plan's rationale (no second mechanism) still holds; only its shape assumption did not.\n *\n * ## Fail fast, never wait\n *\n * A second writer that WAITED would block `exec` behind a TUI session that can last hours. The typed\n * error lets the caller choose: fork to a new id, or give up with a real diagnosis.\n *\n * @internal\n */\n\nimport {\n closeSync,\n existsSync,\n fchmodSync,\n openSync,\n readFileSync,\n rmSync,\n writeSync,\n} from \"node:fs\";\nimport { hostname } from \"node:os\";\n\nimport { TheokitAgentError } from \"../../errors.js\";\n\n/**\n * M81 — another process already holds the writer lease for this session.\n *\n * Carries `sessionPath` because knowing WHICH session is busy is what lets the caller decide between\n * forking and waiting for the user to close the TUI (`rules/error-handling.md § 2` — context enough\n * to act on).\n */\nexport class SessionBusyError extends TheokitAgentError {\n override readonly name = \"SessionBusyError\";\n\n constructor(readonly sessionPath: string) {\n super(\n `another process is already writing this session: ${sessionPath}. ` +\n \"Fork it to a new id instead of appending — two writers interleave lines into a sequence \" +\n \"that parses but is not what either process wrote.\",\n { code: \"session_busy\", isRetryable: false },\n );\n }\n}\n\n/** A held writer lease. `release()` is idempotent. */\nexport interface SessionWriterLease {\n readonly sessionPath: string;\n release(): Promise<void>;\n}\n\n/**\n * Take the exclusive writer lease for `sessionPath`, or reject with {@link SessionBusyError}.\n *\n * The lock is a sibling `.lock` file created with `wx` — the same file-existence primitive the\n * SDK's `withFileLock` builds on. Exclusivity comes from the filesystem, so it holds across\n * processes, not just across async tasks in one process.\n */\n/**\n * Janela de obsolescência **entre máquinas** — e só entre elas.\n *\n * 30 s a partir da AQUISIÇÃO. O nome \"heartbeat\" seria mentira: o registro é gravado uma vez, na\n * tomada do lease, e **não** é renovado a cada escrita. A versão anterior deste comentário dizia\n * \"o dono toca o arquivo a cada aquisição, então um processo ativo nunca cruza a janela\" — falso\n * duas vezes, e a revisão adversarial mediu as duas.\n *\n * No **mesmo host** isso não importa: `reclamavel` decide pelo `pid`, que é exato, e a idade não\n * entra na conta. Entre hosts, importa e é um limite real: um dono remoto **vivo** perde o lease\n * depois de 30 s, porque não há como perguntar a outra máquina se o processo dela existe.\n *\n * **Resíduo declarado, não mecanizado** (a disciplina de `error-handling.md § 4`): renovar o\n * registro a cada append fecharia essa janela, ao custo de um `write` por turno num arquivo que\n * hoje é escrito uma vez por sessão. Não foi feito, e o caso multi-host não é o que motiva este\n * milestone — o enunciado é `exec` e TUI na mesma máquina.\n */\nexport const JANELA_DE_HEARTBEAT_MS = 30_000;\n\n/** Quem detém o lock. Gravado como JSON no `.writer.lock`. */\ninterface DonoDoLock {\n pid: number;\n hostname: string;\n mtime: number;\n}\n\n/**\n * Grava o dono com permissão restrita ao usuário.\n *\n * `0600` porque o lock é uma afirmação de POSSE: com o `0664` que o umask usual produz, outro\n * usuário do mesmo grupo pode sobrescrever o arquivo e forjar a posse da sessão — e a partir daí o\n * dono legítimo é quem passa a receber `SessionBusyError`. O conteúdo (`pid`, `hostname`) é de\n * baixa sensibilidade; o que a permissão protege é a **integridade** do sinal, não o sigilo dele.\n */\nfunction gravarDono(lockPath: string, dono: DonoDoLock): void {\n const fd = openSync(lockPath, \"w\", 0o600);\n try {\n writeSync(fd, JSON.stringify(dono));\n // O `mode` do `open` só vale na CRIAÇÃO. Um `.writer.lock` herdado de uma versão anterior — ou\n // deixado por um processo com umask diferente — continuaria `0664` depois de reclamado, e a\n // janela de forja que o modo fecha para locks novos seguiria aberta para os antigos.\n fchmodSync(fd, 0o600);\n } finally {\n closeSync(fd);\n }\n}\n\n/** Lê o dono do lock. `undefined` quando o arquivo sumiu ou o conteúdo é ilegível. */\nfunction lerDono(lockPath: string): DonoDoLock | undefined {\n let bruto: string;\n try {\n bruto = readFileSync(lockPath, \"utf8\");\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n // `ENOENT` — sumiu entre o `EEXIST` e a leitura. Corrida benigna: o lock não existe mais.\n if (code === \"ENOENT\") return undefined;\n // `EISDIR` — o caminho do lock é um DIRETÓRIO. Nenhum processo desta biblioteca cria um; é\n // lixo de outra coisa, e nunca vai virar um lock legível. Fail-closed aqui trancaria a sessão\n // para sempre. Tratado como sem-dono: a aquisição segue e falha com o erro de FS real, que\n // diz o que está errado — em vez de um SessionBusyError permanente que não diz nada. NÃO é\n // \"reclamável\": o lock não é removido, e quem chama segue sem lease.\n if (code === \"EISDIR\") return undefined;\n // Qualquer outra falha de leitura (`EACCES` num diretório compartilhado, `EIO`) é diferente em\n // espécie: o lock **existe** e nós é que não conseguimos ler o dono. Tratar como livre faria\n // dois escritores conviverem — precisamente o que o lease existe para impedir —, e o `0600`\n // que protege o lock contra forja AMPLIA essa superfície: num diretório compartilhado, o lock\n // do outro usuário é ilegível por desenho.\n //\n // Não saber quem é o dono não é o mesmo que não haver dono. Fail-closed.\n throw new SessionBusyError(lockPath.replace(/\\.writer\\.lock$/, \"\"));\n }\n try {\n const d = JSON.parse(bruto) as Partial<DonoDoLock>;\n if (\n typeof d.pid !== \"number\" ||\n typeof d.hostname !== \"string\" ||\n typeof d.mtime !== \"number\"\n ) {\n return undefined;\n }\n return { pid: d.pid, hostname: d.hostname, mtime: d.mtime };\n } catch {\n // JSON ilegível: um lock que ninguém consegue interpretar não pode trancar a sessão para\n // sempre. Tratar como obsoleto é a escolha recuperável; o custo é o mesmo de um lock velho.\n return undefined;\n }\n}\n\n/** O processo existe? `signal 0` não envia nada — só consulta permissão/existência. */\nfunction processoVivo(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n // EPERM significa que ele EXISTE e é de outro usuário.\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/**\n * O lock pode ser tomado de quem o detém?\n *\n * ADR-2 do plano: reclamar por `pid` sozinho tem falso positivo entre máquinas — o mesmo número\n * existe noutro host, apontando para um processo sem relação. Então:\n *\n * - **mesmo host:** o `pid` é autoritativo, e **só** ele. Processo morto ⇒ reclamável na hora;\n * processo vivo ⇒ nunca, por mais velho que o lock esteja.\n * - **outro host:** o `pid` não diz nada aqui. Só a janela de heartbeat vale, porque é o único\n * sinal que não mente entre máquinas.\n *\n * ## Por que a idade NÃO conta no mesmo host\n *\n * A primeira versão fazia `velho || !processoVivo(pid)`, e isso era um defeito grave: o `mtime` é\n * gravado na **aquisição** e não é tocado a cada append, então qualquer sessão que durasse mais que\n * a janela — isto é, **toda sessão real** — passava a ser roubável por outro processo. Dois\n * escritores no mesmo transcript é exatamente o que o lease existe para impedir.\n *\n * No mesmo host a pergunta \"o dono ainda existe?\" tem resposta exata, e a idade não acrescenta\n * informação nenhuma a ela — só um modo de errar. Manter a janela ali seria heurística por cima de\n * um fato.\n */\nfunction reclamavel(dono: DonoDoLock | undefined): boolean {\n if (dono === undefined) return true; // ilegível ou sumido\n if (dono.hostname !== hostname()) {\n return Date.now() - dono.mtime > JANELA_DE_HEARTBEAT_MS;\n }\n return !processoVivo(dono.pid);\n}\n\n/**\n * A sessão tem escritor **agora**? Consulta que NÃO toma o lease.\n *\n * M95 — existe porque perguntar tomando cria a disputa que se queria detectar: dois processos\n * consultando uma sessão **livre** ao mesmo tempo faziam um deles perder, e o consumidor forkava\n * sem motivo. Medido na revisão adversarial: `CORRIDA: forks espurios = 1`.\n *\n * É uma foto, não uma garantia: entre a consulta e a aquisição real alguém pode tomar a sessão.\n * Quem precisa da garantia usa {@link acquireSessionWriter}; quem precisa **decidir um id antes de\n * abrir nada** usa isto, e trata a corrida onde ela aparece.\n *\n * @internal\n */\nexport function sessaoTemEscritor(sessionPath: string): boolean {\n const lockPath = `${sessionPath}.writer.lock`;\n if (!existsSync(lockPath)) return false;\n try {\n return !reclamavel(lerDono(lockPath));\n } catch {\n // `lerDono` lança quando o lock existe e não pode ser lido — fail-closed, mesma razão de lá:\n // não saber quem é o dono não é o mesmo que não haver dono.\n return true;\n }\n}\n\nexport async function acquireSessionWriter(sessionPath: string): Promise<SessionWriterLease> {\n // M95 — `.writer.lock`, NÃO `.lock`.\n //\n // `withFileLock(path, fn)` já usa `<path>.lock` como companheiro. Enquanto o lease não era\n // chamado de lugar nenhum (o defeito que este milestone corrige) a colisão era teórica; ligá-lo\n // ao mesmo arquivo faria o lease de vida longa bloquear toda seção crítica curta do mesmo path.\n //\n // Dois arquivos porque são duas coisas: `withFileLock` protege uma seção com início e fim; o\n // lease é POSSE, mantida através de turnos, com `release()` explícito — a distinção que o\n // docstring do M81 acima já explica.\n const lockPath = `${sessionPath}.writer.lock`;\n const meu: DonoDoLock = { pid: process.pid, hostname: hostname(), mtime: Date.now() };\n let fd: number;\n try {\n // O modo vale na CRIAÇÃO — o `w` de `gravarDono` não altera arquivo existente.\n fd = openSync(lockPath, \"wx\", 0o600);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n // M95 — o lock existe. Até aqui isso bastava para recusar, e era o defeito: uma TUI morta por\n // SIGKILL trancava o usuário para fora da própria sessão PERMANENTEMENTE, sem caminho de\n // recuperação documentado. Agora o lock diz quem é o dono, e um dono morto cede o lugar.\n if (!reclamavel(lerDono(lockPath))) throw new SessionBusyError(sessionPath);\n gravarDono(lockPath, meu);\n return criarLease(sessionPath, lockPath);\n }\n closeSync(fd);\n gravarDono(lockPath, meu);\n\n return criarLease(sessionPath, lockPath);\n}\n\n/** O lease em si — `release()` idempotente. Extraído porque a aquisição tem dois caminhos de saída. */\nfunction criarLease(sessionPath: string, lockPath: string): SessionWriterLease {\n let released = false;\n return {\n sessionPath,\n release: async (): Promise<void> => {\n if (released) return;\n released = true;\n rmSync(lockPath, { force: true });\n },\n };\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/persistence/session-writer.ts"],"names":["TheokitAgentError","openSync","writeSync","fchmodSync","closeSync","readFileSync","hostname","existsSync","rmSync"],"mappings":";;;;;;AAmDO,IAAM,gBAAA,GAAN,cAA+BA,mCAAA,CAAkB;AAAA,EAGtD,YAAqB,WAAA,EAAqB;AACxC,IAAA,KAAA;AAAA,MACE,oDAAoD,WAAW,CAAA,gJAAA,CAAA;AAAA,MAG/D,EAAE,IAAA,EAAM,cAAA,EAAgB,WAAA,EAAa,KAAA;AAAM,KAC7C;AANmB,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AAAA,EAOrB;AAAA,EAPqB,WAAA;AAAA,EAFH,IAAA,GAAO,kBAAA;AAU3B;AAgCO,IAAM,sBAAA,GAAyB,GAAA;AAiBtC,SAAS,UAAA,CAAW,UAAkB,IAAA,EAAwB;AAC5D,EAAA,MAAM,EAAA,GAAKC,WAAA,CAAS,QAAA,EAAU,GAAA,EAAK,GAAK,CAAA;AACxC,EAAA,IAAI;AACF,IAAAC,YAAA,CAAU,EAAA,EAAI,IAAA,CAAK,SAAA,CAAU,IAAI,CAAC,CAAA;AAIlC,IAAAC,aAAA,CAAW,IAAI,GAAK,CAAA;AAAA,EACtB,CAAA,SAAE;AACA,IAAAC,YAAA,CAAU,EAAE,CAAA;AAAA,EACd;AACF;AAGA,SAAS,QAAQ,QAAA,EAA0C;AACzD,EAAA,IAAI,KAAA;AACJ,EAAA,IAAI;AACF,IAAA,KAAA,GAAQC,eAAA,CAAa,UAAU,MAAM,CAAA;AAAA,EACvC,SAAS,GAAA,EAAK;AACZ,IAAA,MAAM,OAAQ,GAAA,CAA8B,IAAA;AAE5C,IAAA,IAAI,IAAA,KAAS,UAAU,OAAO,MAAA;AAM9B,IAAA,IAAI,IAAA,KAAS,UAAU,OAAO,MAAA;AAQ9B,IAAA,MAAM,IAAI,gBAAA,CAAiB,QAAA,CAAS,OAAA,CAAQ,iBAAA,EAAmB,EAAE,CAAC,CAAA;AAAA,EACpE;AACA,EAAA,IAAI;AACF,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,KAAK,CAAA;AAC1B,IAAA,IACE,OAAO,CAAA,CAAE,GAAA,KAAQ,QAAA,IACjB,OAAO,CAAA,CAAE,QAAA,KAAa,QAAA,IACtB,OAAO,CAAA,CAAE,KAAA,KAAU,QAAA,EACnB;AACA,MAAA,OAAO,KAAA,CAAA;AAAA,IACT;AACA,IAAA,OAAO,EAAE,KAAK,CAAA,CAAE,GAAA,EAAK,UAAU,CAAA,CAAE,QAAA,EAAU,KAAA,EAAO,CAAA,CAAE,KAAA,EAAM;AAAA,EAC5D,CAAA,CAAA,MAAQ;AAGN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAGA,SAAS,aAAa,GAAA,EAAsB;AAC1C,EAAA,IAAI;AACF,IAAA,OAAA,CAAQ,IAAA,CAAK,KAAK,CAAC,CAAA;AACnB,IAAA,OAAO,IAAA;AAAA,EACT,SAAS,GAAA,EAAK;AAEZ,IAAA,OAAQ,IAA8B,IAAA,KAAS,OAAA;AAAA,EACjD;AACF;AAwBA,SAAS,WAAW,IAAA,EAAuC;AACzD,EAAA,IAAI,IAAA,KAAS,QAAW,OAAO,IAAA;AAC/B,EAAA,IAAI,IAAA,CAAK,QAAA,KAAaC,WAAA,EAAS,EAAG;AAChC,IAAA,OAAO,IAAA,CAAK,GAAA,EAAI,GAAI,IAAA,CAAK,KAAA,GAAQ,sBAAA;AAAA,EACnC;AACA,EAAA,OAAO,CAAC,YAAA,CAAa,IAAA,CAAK,GAAG,CAAA;AAC/B;AAeO,SAAS,kBAAkB,WAAA,EAA8B;AAC9D,EAAA,MAAM,QAAA,GAAW,GAAG,WAAW,CAAA,YAAA,CAAA;AAC/B,EAAA,IAAI,CAACC,aAAA,CAAW,QAAQ,CAAA,EAAG,OAAO,KAAA;AAClC,EAAA,IAAI;AACF,IAAA,OAAO,CAAC,UAAA,CAAW,OAAA,CAAQ,QAAQ,CAAC,CAAA;AAAA,EACtC,CAAA,CAAA,MAAQ;AAGN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEA,eAAsB,qBAAqB,WAAA,EAAkD;AAU3F,EAAA,MAAM,QAAA,GAAW,GAAG,WAAW,CAAA,YAAA,CAAA;AAC/B,EAAA,MAAM,GAAA,GAAkB,EAAE,GAAA,EAAK,OAAA,CAAQ,GAAA,EAAK,QAAA,EAAUD,WAAA,EAAS,EAAG,KAAA,EAAO,IAAA,CAAK,GAAA,EAAI,EAAE;AACpF,EAAA,IAAI,EAAA;AACJ,EAAA,IAAI;AAEF,IAAA,EAAA,GAAKL,WAAA,CAAS,QAAA,EAAU,IAAA,EAAM,GAAK,CAAA;AAAA,EACrC,SAAS,GAAA,EAAK;AACZ,IAAA,IAAK,GAAA,CAA8B,IAAA,KAAS,QAAA,EAAU,MAAM,GAAA;AAI5D,IAAA,IAAI,CAAC,WAAW,OAAA,CAAQ,QAAQ,CAAC,CAAA,EAAG,MAAM,IAAI,gBAAA,CAAiB,WAAW,CAAA;AAC1E,IAAA,UAAA,CAAW,UAAU,GAAG,CAAA;AACxB,IAAA,OAAO,UAAA,CAAW,aAAa,QAAQ,CAAA;AAAA,EACzC;AACA,EAAAG,YAAA,CAAU,EAAE,CAAA;AACZ,EAAA,UAAA,CAAW,UAAU,GAAG,CAAA;AAExB,EAAA,OAAO,UAAA,CAAW,aAAa,QAAQ,CAAA;AACzC;AAGA,SAAS,UAAA,CAAW,aAAqB,QAAA,EAAsC;AAC7E,EAAA,IAAI,QAAA,GAAW,KAAA;AACf,EAAA,OAAO;AAAA,IACL,WAAA;AAAA,IACA,SAAS,YAA2B;AAClC,MAAA,IAAI,QAAA,EAAU;AACd,MAAA,QAAA,GAAW,IAAA;AACX,MAAAI,SAAA,CAAO,QAAA,EAAU,EAAE,KAAA,EAAO,IAAA,EAAM,CAAA;AAAA,IAClC;AAAA,GACF;AACF","file":"chunk-P34DHHYQ.cjs","sourcesContent":["/**\n * M81 — single-writer lease for a session transcript.\n *\n * ## The problem\n *\n * Nothing stops two processes appending to the same JSONL transcript. The concrete case: `exec\n * resume --last` can write into the TUI's live session. Two interleaved appends to an append-only\n * file produce lines that are each individually valid and whose SEQUENCE is fiction — and nothing\n * reports it, because every line parses.\n *\n * ## Why an exclusive lockfile rather than `withFileLock`\n *\n * The plan's ADR D2 said to compose `withFileLock`, and that was the right instinct — do not build a\n * second lock mechanism. It turned out not to fit the SHAPE: `withFileLock(path, fn)` is\n * scope-based — it holds the lock for the duration of a callback. A session lease is **held across\n * turns**, for as long as the process owns the session, with an explicit `release()`. Wrapping the\n * whole session lifetime in a callback would invert control of the entire agent loop.\n *\n * So this uses the same underlying primitive `withFileLock` uses (an exclusive-create lockfile,\n * `wx`) with lease semantics on top. That keeps the mechanism single — the file-existence lock —\n * while giving it the lifetime the caller needs. The deviation from D2 is recorded here because the\n * plan's rationale (no second mechanism) still holds; only its shape assumption did not.\n *\n * ## Fail fast, never wait\n *\n * A second writer that WAITED would block `exec` behind a TUI session that can last hours. The typed\n * error lets the caller choose: fork to a new id, or give up with a real diagnosis.\n *\n * @internal\n */\n\nimport {\n closeSync,\n existsSync,\n fchmodSync,\n openSync,\n readFileSync,\n rmSync,\n writeSync,\n} from \"node:fs\";\nimport { hostname } from \"node:os\";\n\nimport { TheokitAgentError } from \"../../errors.js\";\n\n/**\n * M81 — another process already holds the writer lease for this session.\n *\n * Carries `sessionPath` because knowing WHICH session is busy is what lets the caller decide between\n * forking and waiting for the user to close the TUI (`rules/error-handling.md § 2` — context enough\n * to act on).\n */\nexport class SessionBusyError extends TheokitAgentError {\n override readonly name = \"SessionBusyError\";\n\n constructor(readonly sessionPath: string) {\n super(\n `another process is already writing this session: ${sessionPath}. ` +\n \"Fork it to a new id instead of appending — two writers interleave lines into a sequence \" +\n \"that parses but is not what either process wrote.\",\n { code: \"session_busy\", isRetryable: false },\n );\n }\n}\n\n/** A held writer lease. `release()` is idempotent. */\nexport interface SessionWriterLease {\n readonly sessionPath: string;\n release(): Promise<void>;\n}\n\n/**\n * Take the exclusive writer lease for `sessionPath`, or reject with {@link SessionBusyError}.\n *\n * The lock is a sibling `.lock` file created with `wx` — the same file-existence primitive the\n * SDK's `withFileLock` builds on. Exclusivity comes from the filesystem, so it holds across\n * processes, not just across async tasks in one process.\n */\n/**\n * Janela de obsolescência **entre máquinas** — e só entre elas.\n *\n * 30 s a partir da AQUISIÇÃO. O nome \"heartbeat\" seria mentira: o registro é gravado uma vez, na\n * tomada do lease, e **não** é renovado a cada escrita. A versão anterior deste comentário dizia\n * \"o dono toca o arquivo a cada aquisição, então um processo ativo nunca cruza a janela\" — falso\n * duas vezes, e a revisão adversarial mediu as duas.\n *\n * No **mesmo host** isso não importa: `reclamavel` decide pelo `pid`, que é exato, e a idade não\n * entra na conta. Entre hosts, importa e é um limite real: um dono remoto **vivo** perde o lease\n * depois de 30 s, porque não há como perguntar a outra máquina se o processo dela existe.\n *\n * **Resíduo declarado, não mecanizado** (a disciplina de `error-handling.md § 4`): renovar o\n * registro a cada append fecharia essa janela, ao custo de um `write` por turno num arquivo que\n * hoje é escrito uma vez por sessão. Não foi feito, e o caso multi-host não é o que motiva este\n * milestone — o enunciado é `exec` e TUI na mesma máquina.\n */\nexport const JANELA_DE_HEARTBEAT_MS = 30_000;\n\n/** Quem detém o lock. Gravado como JSON no `.writer.lock`. */\ninterface DonoDoLock {\n pid: number;\n hostname: string;\n mtime: number;\n}\n\n/**\n * Grava o dono com permissão restrita ao usuário.\n *\n * `0600` porque o lock é uma afirmação de POSSE: com o `0664` que o umask usual produz, outro\n * usuário do mesmo grupo pode sobrescrever o arquivo e forjar a posse da sessão — e a partir daí o\n * dono legítimo é quem passa a receber `SessionBusyError`. O conteúdo (`pid`, `hostname`) é de\n * baixa sensibilidade; o que a permissão protege é a **integridade** do sinal, não o sigilo dele.\n */\nfunction gravarDono(lockPath: string, dono: DonoDoLock): void {\n const fd = openSync(lockPath, \"w\", 0o600);\n try {\n writeSync(fd, JSON.stringify(dono));\n // O `mode` do `open` só vale na CRIAÇÃO. Um `.writer.lock` herdado de uma versão anterior — ou\n // deixado por um processo com umask diferente — continuaria `0664` depois de reclamado, e a\n // janela de forja que o modo fecha para locks novos seguiria aberta para os antigos.\n fchmodSync(fd, 0o600);\n } finally {\n closeSync(fd);\n }\n}\n\n/** Lê o dono do lock. `undefined` quando o arquivo sumiu ou o conteúdo é ilegível. */\nfunction lerDono(lockPath: string): DonoDoLock | undefined {\n let bruto: string;\n try {\n bruto = readFileSync(lockPath, \"utf8\");\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n // `ENOENT` — sumiu entre o `EEXIST` e a leitura. Corrida benigna: o lock não existe mais.\n if (code === \"ENOENT\") return undefined;\n // `EISDIR` — o caminho do lock é um DIRETÓRIO. Nenhum processo desta biblioteca cria um; é\n // lixo de outra coisa, e nunca vai virar um lock legível. Fail-closed aqui trancaria a sessão\n // para sempre. Tratado como sem-dono: a aquisição segue e falha com o erro de FS real, que\n // diz o que está errado — em vez de um SessionBusyError permanente que não diz nada. NÃO é\n // \"reclamável\": o lock não é removido, e quem chama segue sem lease.\n if (code === \"EISDIR\") return undefined;\n // Qualquer outra falha de leitura (`EACCES` num diretório compartilhado, `EIO`) é diferente em\n // espécie: o lock **existe** e nós é que não conseguimos ler o dono. Tratar como livre faria\n // dois escritores conviverem — precisamente o que o lease existe para impedir —, e o `0600`\n // que protege o lock contra forja AMPLIA essa superfície: num diretório compartilhado, o lock\n // do outro usuário é ilegível por desenho.\n //\n // Não saber quem é o dono não é o mesmo que não haver dono. Fail-closed.\n throw new SessionBusyError(lockPath.replace(/\\.writer\\.lock$/, \"\"));\n }\n try {\n const d = JSON.parse(bruto) as Partial<DonoDoLock>;\n if (\n typeof d.pid !== \"number\" ||\n typeof d.hostname !== \"string\" ||\n typeof d.mtime !== \"number\"\n ) {\n return undefined;\n }\n return { pid: d.pid, hostname: d.hostname, mtime: d.mtime };\n } catch {\n // JSON ilegível: um lock que ninguém consegue interpretar não pode trancar a sessão para\n // sempre. Tratar como obsoleto é a escolha recuperável; o custo é o mesmo de um lock velho.\n return undefined;\n }\n}\n\n/** O processo existe? `signal 0` não envia nada — só consulta permissão/existência. */\nfunction processoVivo(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n // EPERM significa que ele EXISTE e é de outro usuário.\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/**\n * O lock pode ser tomado de quem o detém?\n *\n * ADR-2 do plano: reclamar por `pid` sozinho tem falso positivo entre máquinas — o mesmo número\n * existe noutro host, apontando para um processo sem relação. Então:\n *\n * - **mesmo host:** o `pid` é autoritativo, e **só** ele. Processo morto ⇒ reclamável na hora;\n * processo vivo ⇒ nunca, por mais velho que o lock esteja.\n * - **outro host:** o `pid` não diz nada aqui. Só a janela de heartbeat vale, porque é o único\n * sinal que não mente entre máquinas.\n *\n * ## Por que a idade NÃO conta no mesmo host\n *\n * A primeira versão fazia `velho || !processoVivo(pid)`, e isso era um defeito grave: o `mtime` é\n * gravado na **aquisição** e não é tocado a cada append, então qualquer sessão que durasse mais que\n * a janela — isto é, **toda sessão real** — passava a ser roubável por outro processo. Dois\n * escritores no mesmo transcript é exatamente o que o lease existe para impedir.\n *\n * No mesmo host a pergunta \"o dono ainda existe?\" tem resposta exata, e a idade não acrescenta\n * informação nenhuma a ela — só um modo de errar. Manter a janela ali seria heurística por cima de\n * um fato.\n */\nfunction reclamavel(dono: DonoDoLock | undefined): boolean {\n if (dono === undefined) return true; // ilegível ou sumido\n if (dono.hostname !== hostname()) {\n return Date.now() - dono.mtime > JANELA_DE_HEARTBEAT_MS;\n }\n return !processoVivo(dono.pid);\n}\n\n/**\n * A sessão tem escritor **agora**? Consulta que NÃO toma o lease.\n *\n * M95 — existe porque perguntar tomando cria a disputa que se queria detectar: dois processos\n * consultando uma sessão **livre** ao mesmo tempo faziam um deles perder, e o consumidor forkava\n * sem motivo. Medido na revisão adversarial: `CORRIDA: forks espurios = 1`.\n *\n * É uma foto, não uma garantia: entre a consulta e a aquisição real alguém pode tomar a sessão.\n * Quem precisa da garantia usa {@link acquireSessionWriter}; quem precisa **decidir um id antes de\n * abrir nada** usa isto, e trata a corrida onde ela aparece.\n *\n * @internal\n */\nexport function sessaoTemEscritor(sessionPath: string): boolean {\n const lockPath = `${sessionPath}.writer.lock`;\n if (!existsSync(lockPath)) return false;\n try {\n return !reclamavel(lerDono(lockPath));\n } catch {\n // `lerDono` lança quando o lock existe e não pode ser lido — fail-closed, mesma razão de lá:\n // não saber quem é o dono não é o mesmo que não haver dono.\n return true;\n }\n}\n\nexport async function acquireSessionWriter(sessionPath: string): Promise<SessionWriterLease> {\n // M95 — `.writer.lock`, NÃO `.lock`.\n //\n // `withFileLock(path, fn)` já usa `<path>.lock` como companheiro. Enquanto o lease não era\n // chamado de lugar nenhum (o defeito que este milestone corrige) a colisão era teórica; ligá-lo\n // ao mesmo arquivo faria o lease de vida longa bloquear toda seção crítica curta do mesmo path.\n //\n // Dois arquivos porque são duas coisas: `withFileLock` protege uma seção com início e fim; o\n // lease é POSSE, mantida através de turnos, com `release()` explícito — a distinção que o\n // docstring do M81 acima já explica.\n const lockPath = `${sessionPath}.writer.lock`;\n const meu: DonoDoLock = { pid: process.pid, hostname: hostname(), mtime: Date.now() };\n let fd: number;\n try {\n // O modo vale na CRIAÇÃO — o `w` de `gravarDono` não altera arquivo existente.\n fd = openSync(lockPath, \"wx\", 0o600);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n // M95 — o lock existe. Até aqui isso bastava para recusar, e era o defeito: uma TUI morta por\n // SIGKILL trancava o usuário para fora da própria sessão PERMANENTEMENTE, sem caminho de\n // recuperação documentado. Agora o lock diz quem é o dono, e um dono morto cede o lugar.\n if (!reclamavel(lerDono(lockPath))) throw new SessionBusyError(sessionPath);\n gravarDono(lockPath, meu);\n return criarLease(sessionPath, lockPath);\n }\n closeSync(fd);\n gravarDono(lockPath, meu);\n\n return criarLease(sessionPath, lockPath);\n}\n\n/** O lease em si — `release()` idempotente. Extraído porque a aquisição tem dois caminhos de saída. */\nfunction criarLease(sessionPath: string, lockPath: string): SessionWriterLease {\n let released = false;\n return {\n sessionPath,\n release: async (): Promise<void> => {\n if (released) return;\n released = true;\n rmSync(lockPath, { force: true });\n },\n };\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/diagnostics.ts","../src/internal/security/redact.ts"],"names":[],"mappings":"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","file":"chunk-TAPQWDW2.js","sourcesContent":["/**\n * Canal único de diagnóstico da biblioteca — silencioso por padrão (#147).\n *\n * ## O problema\n *\n * O SDK escrevia diagnósticos direto em `process.stderr` a partir de caminhos quentes —\n * 92 sítios em 51 arquivos sob `internal/`. Numa host de TUI (Ink, alternate screen), essas\n * escritas se intercalam com o render e **corrompem o frame**. E o host não tinha como\n * interceptá-las: não havia logger injetável. Um consumidor chegou a instalar\n * `proper-lockfile` só para calar UMA delas.\n *\n * Uma biblioteca não pode assumir que `stdout`/`stderr` são sinks livres. Quem é dono do\n * terminal é a aplicação, não a dependência.\n *\n * ## O contrato\n *\n * - **`setDiagnosticsSink(fn)`** entrega as mensagens à aplicação, que decide onde colocá-las\n * (uma linha de status, um arquivo, um painel). É o que faltava para uma TUI conviver com o SDK.\n * - **Sem sink, vai para o `stderr`** — exatamente como antes desta mudança.\n *\n * ## Por que o padrão NÃO mudou (ainda), e isso é deliberado\n *\n * A #147 pede silêncio por padrão, e é o alvo certo. Mas **58 arquivos de teste** asseram hoje\n * que estes diagnósticos chegam ao `stderr` — eles codificam o contrato \"o aviso É emitido\", que\n * continua valendo e não deve ser perdido. Virar o padrão sem migrá-los reprovaria ~53 testes, e\n * migrá-los às pressas, em quatro formas diferentes de espionar o `stderr`, é a receita para\n * enfraquecer 58 suítes de uma vez.\n *\n * Então esta mudança entrega a metade que DESTRAVA o consumidor — o host de TUI agora consegue\n * interceptar, que era o bloqueio relatado (\"no way to intercept these; no injectable logger\") — e\n * deixa a virada do padrão como migração própria, com custo medido. Um host que quer silêncio\n * hoje instala `setDiagnosticsSink(() => {})`.\n *\n * ## O que isto NÃO é\n *\n * Não é um logger com níveis, formatação ou destinos múltiplos. É o mínimo que resolve o\n * bloqueio relatado; um logger completo aqui seria inventar requisito que ninguém pediu.\n */\n\n/** Recebe cada mensagem de diagnóstico já formatada, com o `\\n` final. */\nexport type DiagnosticsSink = (message: string) => void;\n\nlet sink: DiagnosticsSink | undefined;\n\n/**\n * Instala (ou remove, passando `undefined`) o destino dos diagnósticos.\n *\n * Quando há sink, ele é o ÚNICO destino — o `stderr` não recebe cópia. Duplicar destinos\n * devolveria o problema à TUI que instalou o sink justamente para tirar as mensagens do terminal.\n */\nexport function setDiagnosticsSink(next: DiagnosticsSink | undefined): void {\n sink = next;\n}\n\n/**\n * Emite uma mensagem de diagnóstico da biblioteca.\n *\n * Substitui `process.stderr.write` nos caminhos internos. Nunca lança: um sink defeituoso não\n * pode derrubar o run que ele apenas observa.\n */\nexport function diag(message: string): void {\n if (sink !== undefined) {\n try {\n sink(message);\n } catch {\n // Observabilidade nunca quebra o run — mesmo princípio de `emitRunEvent`.\n }\n return;\n }\n process.stderr.write(message);\n}\n","import { diag } from \"../diagnostics.js\";\n\n/**\n * Canonical secret redaction module (ADRs D68-D73).\n *\n * Single source of truth for credential pattern masking across the SDK.\n * Wired at output boundaries: `ErrorMetadata.raw` (mappers/shared.ts),\n * telemetry span attributes (telemetry/tracer.ts), transcript JSONL\n * appends (agent-session-store.ts), migration logger output\n * (memory/migrate-sqlite-to-lance.ts).\n *\n * - D68: central module, single source of truth (replaces 2 duplicates)\n * - D69: env snapshot at module init (prompt-injection defense)\n * - D70: ON by default, warn on opt-out\n * - D71: two-bucket masking — short fully masked, long preserves prefix+suffix\n * - D72: `codeFile` opt-out for legitimate prefix-shaped content\n * - D73: redact at OUTPUT boundaries, not at storage\n *\n * @internal\n */\n\n// D69: env snapshot captured at module load. Subsequent mutations of\n// process.env.THEOKIT_REDACT_SECRETS are ignored — defends against\n// prompt injection that tries to disable redaction mid-run.\nlet REDACT_ENABLED: boolean = readEnvOnce();\n\nfunction readEnvOnce(): boolean {\n const raw = process.env.THEOKIT_REDACT_SECRETS;\n if (raw === undefined) return true; // D70: default ON\n return [\"1\", \"true\", \"yes\", \"on\"].includes(raw.toLowerCase());\n}\n\n// D70: warn once on opt-out so the user knows they're vulnerable.\nlet warnedOptOut = false;\nif (!REDACT_ENABLED && !warnedOptOut) {\n diag(\n \"[theokit-sdk] Secret redaction is DISABLED via THEOKIT_REDACT_SECRETS. \" +\n \"Credentials may leak into errors, telemetry, logs, transcripts.\\n\",\n );\n warnedOptOut = true;\n}\n\n/**\n * Built-in credential patterns. Order matters — more specific prefixes\n * must come before generic ones (e.g., `sk-ant-` before `sk-`). Quantifiers\n * are all bounded `{n,m}` or applied to char classes — linear time, no ReDoS.\n *\n * @internal\n */\nconst BUILTIN_PATTERNS: readonly RegExp[] = [\n // T5.4: 30+ vendor prefixes (was 12 pre-T5.4). Order matters — more\n // specific prefixes precede generic ones (e.g., sk-ant-admin01 before\n // sk-ant-, sk-proj- before sk-). PEM block deliberately first so its\n // multi-line span runs before any per-line patterns can fire.\n /-----BEGIN[ ]+(?:RSA |EC |DSA |OPENSSH |ENCRYPTED |)PRIVATE KEY-----[\\s\\S]+?-----END[ ]+(?:RSA |EC |DSA |OPENSSH |ENCRYPTED |)PRIVATE KEY-----/g,\n // JWT — exact 3-segment base64url. Dotted; the body floor of 4 chars per\n // segment matches the minimum legal payload while skipping `a.b.c` noise.\n /eyJ[A-Za-z0-9_-]{4,}\\.eyJ[A-Za-z0-9_-]{4,}\\.[A-Za-z0-9_-]{4,}/g,\n // Azure Storage SAS — match the sig= component (URL-encoded base64).\n /(?<=[?&]sig=)[A-Za-z0-9%+/]{20,}/g,\n // Anthropic\n /sk-ant-admin01-[A-Za-z0-9_-]{10,}/g, // Anthropic admin keys (must precede sk-ant-)\n /sk-ant-[A-Za-z0-9_-]{10,}/g, // Anthropic regular\n // OpenAI family + clones (sk- generic must come AFTER all sk-foo- variants)\n /sk-proj-[A-Za-z0-9_-]{10,}/g, // OpenAI project key (must precede sk- generic)\n /sk-[A-Za-z0-9_-]{10,}/g, // OpenAI / OpenRouter / DeepInfra / Together / DeepSeek\n // Provider prefixes (alphabetized for maintainability)\n /AIza[A-Za-z0-9_-]{35}/g, // Google API key\n /AKIA[A-Z0-9]{16}/g, // AWS access key\n /fw_[A-Za-z0-9]{20,}/g, // Fireworks\n /glpat-[A-Za-z0-9_-]{20}/g, // GitLab PAT\n /ghp_[A-Za-z0-9]{36}/g, // GitHub PAT classic\n /github_pat_[A-Za-z0-9_]{82}/g, // GitHub PAT fine-grained\n /gsk_[A-Za-z0-9]{20,}/g, // Groq\n /hf_[A-Za-z0-9]{20,}/g, // HuggingFace\n /\\bpa-[A-Za-z0-9_-]{20,}/g, // Voyage AI (word-boundary to skip CSS / kebab IDs)\n /pcsk_[A-Za-z0-9_-]{20,}/g, // Pinecone\n /pplx-[A-Za-z0-9_-]{20,}/g, // Perplexity\n /r8_[A-Za-z0-9_-]{20,}/g, // Replicate\n /rk_live_[A-Za-z0-9]{20,}/g, // Stripe restricted\n /sk_live_[A-Za-z0-9]{20,}/g, // Stripe secret\n /sntrys_[A-Za-z0-9]{40,}/g, // Sentry user auth\n /xai-[A-Za-z0-9_-]{20,}/g, // xAI (Grok)\n /xox[bpasr]-[A-Za-z0-9-]{10,}/g, //Slack tokens\n // Additional unique-prefix tokens with low false-positive risk\n /npm_[A-Za-z0-9]{36}/g, // npm access token\n /SG\\.[A-Za-z0-9_-]{22}\\.[A-Za-z0-9_-]{43}/g, // SendGrid\n /\\bSK[A-Za-z0-9]{32}\\b/g, // Twilio API SID (word-boundary to skip CSS class noise)\n /\\bkey-[a-f0-9]{32}\\b/g, // Mailgun (hex-only narrows false positives)\n /MT[A-Za-z0-9_-]{23}\\.[A-Za-z0-9_-]{6}\\.[A-Za-z0-9_-]{27}/g, // Discord bot\n /\\b(?:sdk|mob)-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\\b/g, // LaunchDarkly\n];\n\n// `Bearer <token>` matched as its own first-class pattern so PARAM_PATTERN\n// doesn't have to handle the unusual `Authorization: Bearer xxx` shape\n// (no `:` or `=` between \"Bearer\" and the value — bare whitespace).\nconst BEARER_PATTERN = /\\b(Bearer\\s+)([A-Za-z0-9_\\-.+/=]{8,})/g;\n\n// Parametric: matches `key=value` and `key: value` (with optional quote\n// between the key and the separator, to handle JSON: `\"api_key\": \"...\"`)\n// in URLs, query strings, JSON-like bodies, HTTP headers. Captures the\n// prefix so we keep it visible while masking the value.\n//\n// `authorization` deliberately excluded — BEARER_PATTERN handles the\n// common `Authorization: Bearer xxx` shape. Including it here causes\n// double-masking (\"Authorization: *** ***\") after Bearer fires.\n// T5.4: keyword set expanded from 6 → 16 to cover the OAuth / JWT / generic\n// credential vocabulary surfaced by DR6 finding #4. `authorization`,\n// `auth`, `bearer` stay excluded — BEARER_PATTERN handles the\n// `Authorization: Bearer xxx` shape and including these here would\n// re-catch the post-BUILTIN-masked form (D71 prefix-preservation\n// contract) and double-mask to `***`.\n//\n// Value class includes `.` so JWT / `.env` / dotted base64url values\n// match; the callback skips already-masked values (containing the\n// `...` D71 separator) to preserve the BUILTIN prefix-mask result.\nconst PARAM_PATTERN =\n /(\\b(?:access_token|api_key|api-key|client_secret|credential|credentials|id_token|jwt|password|private_key|refresh_token|secret|service_account|session_token|token|x-api-key)\\b[\"']?\\s*[:=]\\s*[\"']?)([A-Za-z0-9_\\-.+/]+)/gi;\n\nconst _extraPatterns: RegExp[] = [];\n\n/**\n * Add a user-defined redaction pattern. Additive — never removes builtins.\n * Throws if the regex lacks the `/g` flag (without `/g`, `.replace` only\n * substitutes the first match and the rest leaks).\n *\n * @internal — exposed publicly via `Security.addPattern` in `src/security.ts`.\n */\nexport function addPattern(re: RegExp): void {\n if (!re.global) {\n throw new Error(\"Security.addPattern: regex must have /g flag for replace-all semantics\");\n }\n _extraPatterns.push(re);\n}\n\n/**\n * Two-bucket masking (D71):\n * - tokens shorter than 18 chars → fully masked as `***`\n * - tokens >= 18 chars → keep first 6 + `...` + last 4\n *\n * Rationale: long tokens are unique per-account; prefix+suffix preserves\n * debuggability without revealing the secret middle.\n *\n * @internal\n */\nexport function maskToken(token: string): string {\n if (token.length < 18) return \"***\";\n return `${token.slice(0, 6)}...${token.slice(-4)}`;\n}\n\n// issue #117 — the EXACT shape of a `maskToken` output (`slice(0,6)+\"...\"+slice(-4)`\n// = 6 chars, literal `...`, 4 chars). Used to detect a value PARAM_PATTERN would\n// otherwise re-mask, WITHOUT skipping a raw secret that merely contains `...`.\nconst MASK_SHAPE = /^.{6}\\.\\.\\..{4}$/s;\n\n/**\n * Redact known credential patterns from `text`. Default behavior masks\n * builtins + extras + parametric `key=value` sinks.\n *\n * With `{ codeFile: true }` (D72), skips PARAM_PATTERN to avoid mangling\n * `.env.example`, schema JSON, or test fixtures that legitimately contain\n * prefix-like strings.\n *\n * Returns the redacted string. Coerces non-strings via JSON.stringify;\n * EC-7 fix (edge-case review): wraps in try/catch so circular references\n * never propagate — returns sentinel `\"[unredactable: circular]\"`.\n *\n * @internal\n */\n// Coerce arbitrary input to a string for redaction. Returns `null`\n// sentinel when the value is null/undefined/non-stringifiable, so the\n// caller can short-circuit with `\"\"`. EC-7 fix: circular refs go through\n// the try/catch and produce the sentinel marker, never throwing.\nfunction coerceToString(value: unknown): string | null {\n if (typeof value === \"string\") return value;\n if (value === null || value === undefined) return null;\n if (typeof value === \"object\") {\n try {\n const s = JSON.stringify(value);\n return s === undefined ? null : s;\n } catch {\n return \"[unredactable: circular]\";\n }\n }\n return String(value);\n}\n\nexport function redactSecrets(text: unknown, opts?: { codeFile?: boolean }): string {\n const coerced = coerceToString(text);\n if (coerced === null) return \"\";\n if (!REDACT_ENABLED) return coerced;\n\n let s = coerced;\n for (const re of BUILTIN_PATTERNS) {\n s = s.replace(re, (m) => maskToken(m));\n }\n for (const re of _extraPatterns) {\n s = s.replace(re, (m) => maskToken(m));\n }\n if (!opts?.codeFile) {\n // Bearer first (preserves \"Bearer \" prefix, masks the token after).\n // Must run before PARAM_PATTERN so the bare-whitespace shape doesn't\n // get mis-handled as a value.\n s = s.replace(BEARER_PATTERN, (_, prefix: string) => `${prefix}***`);\n // T5.4: skip if value already contains the D71 bucket-mask separator\n // (`...`) — BUILTIN ran first and produced a prefix-preserved mask;\n // re-masking would lose the prefix and degrade debuggability.\n s = s.replace(PARAM_PATTERN, (whole, prefix: string, value: string) => {\n // issue #117 — skip ONLY when the value is already a mask that a BUILTIN\n // pattern produced (maskToken's exact `6chars...4chars` shape), so we don't\n // re-mask and lose the prefix. The old `value.includes(\"...\")` was too broad:\n // a REAL secret that happens to contain `...` (e.g. `L_-cxw-.2UI_..._`) was\n // skipped and LEAKED. The mask shape is exact (maskToken: slice(0,6)+\"...\"+\n // slice(-4)), so a raw secret with `...` elsewhere is now masked.\n if (MASK_SHAPE.test(value)) return whole;\n return `${prefix}***`;\n });\n }\n return s;\n}\n\n/**\n * Test-only helper exported for `_test-reset.ts`. NOT included in the\n * `index.ts` barrel — vitest setup imports the dedicated module via\n * explicit path to discourage production callers.\n *\n * @internal\n */\nexport function _resetForTests(opts: { enabled?: boolean; clearExtras?: boolean }): void {\n if (opts.enabled !== undefined) REDACT_ENABLED = opts.enabled;\n if (opts.clearExtras === true) _extraPatterns.length = 0;\n}\n\n/**\n * T5.4 — Test-only count of BUILTIN_PATTERNS. Exposed so the count-floor\n * assertion can run without re-deriving the array shape in test land.\n * NOT included in the public barrel.\n *\n * @internal\n */\nexport function __TESTING__BUILTIN_PATTERN_COUNT(): number {\n return BUILTIN_PATTERNS.length;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/judge/parse-verdict.ts","../src/internal/judge/judge-call.ts"],"names":["TheokitAgentError"],"mappings":"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","file":"chunk-THEYGCYS.cjs","sourcesContent":["/**\n * Pure verdict parser (T2.1, ADRs D120-D121).\n *\n * Strict prefix matching against `DONE:`, `CONTINUE:`, `SKIPPED:`. Reason\n * is the suffix, trimmed. Anything else is fail-safe: verdict = `continue`,\n * `parseFailed: true` — caller's max-consecutive-failure cap stops the\n * loop after N flakes.\n *\n * @internal\n */\n\nimport type { JudgeResult } from \"../../types/goal-events.js\";\n\nconst DONE_PREFIX = \"DONE:\";\nconst CONTINUE_PREFIX = \"CONTINUE:\";\nconst SKIPPED_PREFIX = \"SKIPPED:\";\n/** M80 — o judge agora pode declarar impossibilidade, não só \"continue\". */\nconst BLOCKED_PREFIX = \"BLOCKED:\";\n\nexport function parseVerdict(text: string): JudgeResult {\n const trimmed = text.trim();\n\n if (trimmed.startsWith(DONE_PREFIX)) {\n return {\n verdict: \"done\",\n reason: trimmed.slice(DONE_PREFIX.length).trim(),\n parseFailed: false,\n };\n }\n if (trimmed.startsWith(CONTINUE_PREFIX)) {\n return {\n verdict: \"continue\",\n reason: trimmed.slice(CONTINUE_PREFIX.length).trim(),\n parseFailed: false,\n };\n }\n if (trimmed.startsWith(BLOCKED_PREFIX)) {\n return {\n verdict: \"blocked\",\n reason: trimmed.slice(BLOCKED_PREFIX.length).trim(),\n parseFailed: false,\n };\n }\n if (trimmed.startsWith(SKIPPED_PREFIX)) {\n return {\n verdict: \"skipped\",\n reason: trimmed.slice(SKIPPED_PREFIX.length).trim(),\n parseFailed: false,\n };\n }\n\n // Fail-safe: ADR D121 — prefer \"continue\" so we don't stop prematurely.\n // The runUntil loop counts consecutive parseFailed responses and bails\n // after `maxConsecutiveJudgeFailures` (default 3).\n return {\n verdict: \"continue\",\n reason: `judge response malformed: \"${trimmed.slice(0, 100)}\"`,\n parseFailed: true,\n };\n}\n","import { TheokitAgentError } from \"../../errors.js\";\n/**\n * Judge call primitive (T2.2, ADRs D119-D121).\n *\n * Instantiates a short-lived auxiliary agent that evaluates whether a\n * goal is satisfied. The auxiliary judge runs with `tools: []` and a\n * cheap model (default `openai/gpt-4o-mini`) — see ADR D119. The judge\n * model needs only API access; we read `OPENROUTER_API_KEY` directly\n * from the environment (EC-A — single source of truth) and let the\n * caller override via {@link JudgeOptions.apiKey} for Anthropic or\n * direct-OpenAI environments.\n *\n * NOTE: judge aux agents created from inside a `forkAgent` context will\n * inherit the parent fork's whitelist via AsyncLocalStorage (EC-J).\n * `tools: []` keeps this benign today; future callers that add tools to\n * a judge should be aware.\n *\n * @internal\n */\n\nimport type { AgentOptions, SDKAgent } from \"../../types/agent.js\";\nimport type { JudgeResult } from \"../../types/goal-events.js\";\nimport { parseVerdict } from \"./parse-verdict.js\";\n\n/** Inputs to the judge — pure data. */\nexport interface JudgeContext {\n goal: string;\n lastResponse: string;\n subgoals?: string[];\n}\n\n/** Caller-supplied tuning knobs for the judge call. */\nexport interface JudgeOptions {\n /**\n * Judge model identifier. Quando ausente, DERIVA de {@link agentModel} — e só cai no literal\n * `\"openai/gpt-4o-mini\"` se nem esse for informado.\n *\n * M80 — o default fixo era provider-cego: ele só resolve em OpenRouter. Com chave Anthropic dá\n * 404, com bearer OAuth dá 401, e em ambos o goal queimava 3 turnos inteiros antes de falhar com\n * razão enganosa. O agent-builder já contornava isso derivando por conta própria; o conhecimento\n * pertence aqui.\n */\n judgeModel?: string;\n /**\n * M80 — o modelo do agente CONDUZIDO. É a base da derivação: um judge que roda no mesmo modelo do\n * chat funciona onde o chat funciona.\n */\n agentModel?: string;\n /** Override env. Default `process.env.OPENROUTER_API_KEY` (EC-A). */\n apiKey?: string;\n}\n\n/**\n * M80 — a credencial ou o modelo do judge não servem: 401/404.\n *\n * Falha RÁPIDA por design. `rules/error-handling.md § 2` separa recuperável de irrecuperável, e um\n * modelo inexistente não passa a existir no retry — dobrar isso em `{parseFailed: true}` fazia o loop\n * tentar três vezes e reportar \"failed\" por limite de falhas consecutivas, escondendo que a causa era\n * credencial. Falha lenta e opaca trocada por rápida e clara.\n *\n * Falha de PARSE e erro de rede continuam dobrados: são recuperáveis, e o loop já decide por falhas\n * consecutivas.\n */\nexport class JudgeCredentialError extends TheokitAgentError {\n override readonly name = \"JudgeCredentialError\";\n\n constructor(\n readonly httpStatus: number,\n readonly judgeModel: string,\n cause: unknown,\n ) {\n super(\n `judge unavailable: model \"${judgeModel}\" returned ${String(httpStatus)}. ` +\n \"Pass `judgeModel`/`apiKey` that resolve for this provider, or omit `judgeModel` to derive \" +\n \"it from the agent being driven.\",\n { code: \"judge_credential\", cause, isRetryable: false },\n );\n }\n}\n\n/** Extrai o status HTTP de um erro de provider, quando ele o carrega. */\nfunction statusHttpDe(err: unknown): number | undefined {\n const s =\n (err as { status?: unknown; statusCode?: unknown }).status ??\n (err as { statusCode?: unknown }).statusCode;\n if (typeof s === \"number\") return s;\n const m = /\\b(401|403|404)\\b/.exec(err instanceof Error ? err.message : String(err));\n return m?.[1] !== undefined ? Number(m[1]) : undefined;\n}\n\n/** Dependencies injected so `judge-call.ts` stays free of `Agent` import. */\nexport interface JudgeDeps {\n create: (options: AgentOptions) => Promise<SDKAgent>;\n}\n\n/**\n * Run the judge auxiliary agent and parse the verdict. Always returns a\n * `JudgeResult` — failures are folded into `{ parseFailed: true,\n * verdict: \"continue\" }` so the loop can decide based on consecutive\n * failures (ADR D121).\n *\n * @internal\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: judge call must (1) check env or override, (2) catch errors as fail-safe, (3) ensure aux dispose runs once regardless of outcome — three concerns linearly arranged, harm clarity to extract.\nexport async function judgeCallImpl(\n ctx: JudgeContext,\n options: JudgeOptions | undefined,\n deps: JudgeDeps,\n): Promise<JudgeResult> {\n const prompt = composeJudgePrompt(ctx);\n // EC-A: single env source — OpenRouter only. No multi-provider fallback.\n const apiKey = options?.apiKey ?? process.env.OPENROUTER_API_KEY;\n if (apiKey === undefined) {\n return {\n verdict: \"continue\",\n reason:\n \"judge unavailable: OPENROUTER_API_KEY missing and no override passed via options.apiKey\",\n parseFailed: true,\n };\n }\n\n // M80 — precedência: explícito > modelo do agente conduzido > o literal histórico. O literal só\n // sobrevive como último recurso, para não quebrar quem nunca passou nenhum dos dois.\n const judgeModel = options?.judgeModel ?? options?.agentModel ?? \"openai/gpt-4o-mini\";\n let auxAgent: SDKAgent | undefined;\n try {\n auxAgent = await deps.create({\n apiKey,\n model: { id: judgeModel },\n tools: [],\n local: {},\n metadata: { forkOrigin: \"judge\" },\n } as AgentOptions);\n const run = await auxAgent.send(prompt);\n const result = await run.wait();\n return parseVerdict(result.result ?? \"\");\n } catch (err) {\n // M80 — 401/403/404 são de credencial/modelo: irrecuperáveis, falham rápido e tipado. Todo o\n // resto (rede, timeout, 5xx) segue dobrado, porque É recuperável e o loop já decide por falhas\n // consecutivas.\n const status = statusHttpDe(err);\n if (status === 401 || status === 403 || status === 404) {\n throw new JudgeCredentialError(status, judgeModel, err);\n }\n return {\n verdict: \"continue\",\n reason: `judge call failed: ${err instanceof Error ? err.message : String(err)}`,\n parseFailed: true,\n };\n } finally {\n if (auxAgent !== undefined) {\n try {\n await auxAgent.dispose();\n } catch {\n // dispose errors are non-fatal; judge result is already prepared\n }\n }\n }\n}\n\n/**\n * Build the strict-format prompt the judge expects.\n *\n * @internal\n */\nexport function composeJudgePrompt(ctx: JudgeContext): string {\n const subgoals =\n ctx.subgoals !== undefined && ctx.subgoals.length > 0 ? ctx.subgoals.join(\", \") : \"(none)\";\n return `You are a goal judge. Determine if this goal is satisfied.\n\nGoal: ${ctx.goal}\nSubgoals: ${subgoals}\nLast agent response: ${ctx.lastResponse}\n\nRespond with EXACTLY one of:\n- DONE: <reason>\n- CONTINUE: <what's left>\n- SKIPPED: <why not applicable>\n\nBe strict. If unclear, prefer CONTINUE.`;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/judge/parse-verdict.ts","../src/internal/judge/judge-call.ts"],"names":[],"mappings":"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","file":"chunk-VNNK6AKU.js","sourcesContent":["/**\n * Pure verdict parser (T2.1, ADRs D120-D121).\n *\n * Strict prefix matching against `DONE:`, `CONTINUE:`, `SKIPPED:`. Reason\n * is the suffix, trimmed. Anything else is fail-safe: verdict = `continue`,\n * `parseFailed: true` — caller's max-consecutive-failure cap stops the\n * loop after N flakes.\n *\n * @internal\n */\n\nimport type { JudgeResult } from \"../../types/goal-events.js\";\n\nconst DONE_PREFIX = \"DONE:\";\nconst CONTINUE_PREFIX = \"CONTINUE:\";\nconst SKIPPED_PREFIX = \"SKIPPED:\";\n/** M80 — o judge agora pode declarar impossibilidade, não só \"continue\". */\nconst BLOCKED_PREFIX = \"BLOCKED:\";\n\nexport function parseVerdict(text: string): JudgeResult {\n const trimmed = text.trim();\n\n if (trimmed.startsWith(DONE_PREFIX)) {\n return {\n verdict: \"done\",\n reason: trimmed.slice(DONE_PREFIX.length).trim(),\n parseFailed: false,\n };\n }\n if (trimmed.startsWith(CONTINUE_PREFIX)) {\n return {\n verdict: \"continue\",\n reason: trimmed.slice(CONTINUE_PREFIX.length).trim(),\n parseFailed: false,\n };\n }\n if (trimmed.startsWith(BLOCKED_PREFIX)) {\n return {\n verdict: \"blocked\",\n reason: trimmed.slice(BLOCKED_PREFIX.length).trim(),\n parseFailed: false,\n };\n }\n if (trimmed.startsWith(SKIPPED_PREFIX)) {\n return {\n verdict: \"skipped\",\n reason: trimmed.slice(SKIPPED_PREFIX.length).trim(),\n parseFailed: false,\n };\n }\n\n // Fail-safe: ADR D121 — prefer \"continue\" so we don't stop prematurely.\n // The runUntil loop counts consecutive parseFailed responses and bails\n // after `maxConsecutiveJudgeFailures` (default 3).\n return {\n verdict: \"continue\",\n reason: `judge response malformed: \"${trimmed.slice(0, 100)}\"`,\n parseFailed: true,\n };\n}\n","import { TheokitAgentError } from \"../../errors.js\";\n/**\n * Judge call primitive (T2.2, ADRs D119-D121).\n *\n * Instantiates a short-lived auxiliary agent that evaluates whether a\n * goal is satisfied. The auxiliary judge runs with `tools: []` and a\n * cheap model (default `openai/gpt-4o-mini`) — see ADR D119. The judge\n * model needs only API access; we read `OPENROUTER_API_KEY` directly\n * from the environment (EC-A — single source of truth) and let the\n * caller override via {@link JudgeOptions.apiKey} for Anthropic or\n * direct-OpenAI environments.\n *\n * NOTE: judge aux agents created from inside a `forkAgent` context will\n * inherit the parent fork's whitelist via AsyncLocalStorage (EC-J).\n * `tools: []` keeps this benign today; future callers that add tools to\n * a judge should be aware.\n *\n * @internal\n */\n\nimport type { AgentOptions, SDKAgent } from \"../../types/agent.js\";\nimport type { JudgeResult } from \"../../types/goal-events.js\";\nimport { parseVerdict } from \"./parse-verdict.js\";\n\n/** Inputs to the judge — pure data. */\nexport interface JudgeContext {\n goal: string;\n lastResponse: string;\n subgoals?: string[];\n}\n\n/** Caller-supplied tuning knobs for the judge call. */\nexport interface JudgeOptions {\n /**\n * Judge model identifier. Quando ausente, DERIVA de {@link agentModel} — e só cai no literal\n * `\"openai/gpt-4o-mini\"` se nem esse for informado.\n *\n * M80 — o default fixo era provider-cego: ele só resolve em OpenRouter. Com chave Anthropic dá\n * 404, com bearer OAuth dá 401, e em ambos o goal queimava 3 turnos inteiros antes de falhar com\n * razão enganosa. O agent-builder já contornava isso derivando por conta própria; o conhecimento\n * pertence aqui.\n */\n judgeModel?: string;\n /**\n * M80 — o modelo do agente CONDUZIDO. É a base da derivação: um judge que roda no mesmo modelo do\n * chat funciona onde o chat funciona.\n */\n agentModel?: string;\n /** Override env. Default `process.env.OPENROUTER_API_KEY` (EC-A). */\n apiKey?: string;\n}\n\n/**\n * M80 — a credencial ou o modelo do judge não servem: 401/404.\n *\n * Falha RÁPIDA por design. `rules/error-handling.md § 2` separa recuperável de irrecuperável, e um\n * modelo inexistente não passa a existir no retry — dobrar isso em `{parseFailed: true}` fazia o loop\n * tentar três vezes e reportar \"failed\" por limite de falhas consecutivas, escondendo que a causa era\n * credencial. Falha lenta e opaca trocada por rápida e clara.\n *\n * Falha de PARSE e erro de rede continuam dobrados: são recuperáveis, e o loop já decide por falhas\n * consecutivas.\n */\nexport class JudgeCredentialError extends TheokitAgentError {\n override readonly name = \"JudgeCredentialError\";\n\n constructor(\n readonly httpStatus: number,\n readonly judgeModel: string,\n cause: unknown,\n ) {\n super(\n `judge unavailable: model \"${judgeModel}\" returned ${String(httpStatus)}. ` +\n \"Pass `judgeModel`/`apiKey` that resolve for this provider, or omit `judgeModel` to derive \" +\n \"it from the agent being driven.\",\n { code: \"judge_credential\", cause, isRetryable: false },\n );\n }\n}\n\n/** Extrai o status HTTP de um erro de provider, quando ele o carrega. */\nfunction statusHttpDe(err: unknown): number | undefined {\n const s =\n (err as { status?: unknown; statusCode?: unknown }).status ??\n (err as { statusCode?: unknown }).statusCode;\n if (typeof s === \"number\") return s;\n const m = /\\b(401|403|404)\\b/.exec(err instanceof Error ? err.message : String(err));\n return m?.[1] !== undefined ? Number(m[1]) : undefined;\n}\n\n/** Dependencies injected so `judge-call.ts` stays free of `Agent` import. */\nexport interface JudgeDeps {\n create: (options: AgentOptions) => Promise<SDKAgent>;\n}\n\n/**\n * Run the judge auxiliary agent and parse the verdict. Always returns a\n * `JudgeResult` — failures are folded into `{ parseFailed: true,\n * verdict: \"continue\" }` so the loop can decide based on consecutive\n * failures (ADR D121).\n *\n * @internal\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: judge call must (1) check env or override, (2) catch errors as fail-safe, (3) ensure aux dispose runs once regardless of outcome — three concerns linearly arranged, harm clarity to extract.\nexport async function judgeCallImpl(\n ctx: JudgeContext,\n options: JudgeOptions | undefined,\n deps: JudgeDeps,\n): Promise<JudgeResult> {\n const prompt = composeJudgePrompt(ctx);\n // EC-A: single env source — OpenRouter only. No multi-provider fallback.\n const apiKey = options?.apiKey ?? process.env.OPENROUTER_API_KEY;\n if (apiKey === undefined) {\n return {\n verdict: \"continue\",\n reason:\n \"judge unavailable: OPENROUTER_API_KEY missing and no override passed via options.apiKey\",\n parseFailed: true,\n };\n }\n\n // M80 — precedência: explícito > modelo do agente conduzido > o literal histórico. O literal só\n // sobrevive como último recurso, para não quebrar quem nunca passou nenhum dos dois.\n const judgeModel = options?.judgeModel ?? options?.agentModel ?? \"openai/gpt-4o-mini\";\n let auxAgent: SDKAgent | undefined;\n try {\n auxAgent = await deps.create({\n apiKey,\n model: { id: judgeModel },\n tools: [],\n local: {},\n metadata: { forkOrigin: \"judge\" },\n } as AgentOptions);\n const run = await auxAgent.send(prompt);\n const result = await run.wait();\n return parseVerdict(result.result ?? \"\");\n } catch (err) {\n // M80 — 401/403/404 são de credencial/modelo: irrecuperáveis, falham rápido e tipado. Todo o\n // resto (rede, timeout, 5xx) segue dobrado, porque É recuperável e o loop já decide por falhas\n // consecutivas.\n const status = statusHttpDe(err);\n if (status === 401 || status === 403 || status === 404) {\n throw new JudgeCredentialError(status, judgeModel, err);\n }\n return {\n verdict: \"continue\",\n reason: `judge call failed: ${err instanceof Error ? err.message : String(err)}`,\n parseFailed: true,\n };\n } finally {\n if (auxAgent !== undefined) {\n try {\n await auxAgent.dispose();\n } catch {\n // dispose errors are non-fatal; judge result is already prepared\n }\n }\n }\n}\n\n/**\n * Build the strict-format prompt the judge expects.\n *\n * @internal\n */\nexport function composeJudgePrompt(ctx: JudgeContext): string {\n const subgoals =\n ctx.subgoals !== undefined && ctx.subgoals.length > 0 ? ctx.subgoals.join(\", \") : \"(none)\";\n return `You are a goal judge. Determine if this goal is satisfied.\n\nGoal: ${ctx.goal}\nSubgoals: ${subgoals}\nLast agent response: ${ctx.lastResponse}\n\nRespond with EXACTLY one of:\n- DONE: <reason>\n- CONTINUE: <what's left>\n- SKIPPED: <why not applicable>\n\nBe strict. If unclear, prefer CONTINUE.`;\n}\n"]}
|